diff --git a/.docker/docker-compose-infra.yml b/.docker/docker-compose-infra.yml index c65699c8d..2719d12ba 100644 --- a/.docker/docker-compose-infra.yml +++ b/.docker/docker-compose-infra.yml @@ -1,14 +1,13 @@ # docker-compose.yml services: - tenant_db: image: postgres:15 - shm_size: '1gb' + shm_size: "1gb" ports: - - '5432:5432' + - "5432:5432" healthcheck: - test: [ "CMD-SHELL", "pg_isready", "-d", "postgres" ] + test: ["CMD-SHELL", "pg_isready", "-d", "postgres"] interval: 5s timeout: 60s retries: 20 @@ -21,12 +20,12 @@ services: multitenant_db: image: postgres:15 ports: - - '5433:5432' + - "5433:5432" configs: - source: init.sql target: /docker-entrypoint-initdb.d/init.sql healthcheck: - test: [ "CMD-SHELL", "pg_isready", "-d", "postgres" ] + test: ["CMD-SHELL", "pg_isready", "-d", "postgres"] interval: 5s timeout: 60s retries: 20 @@ -36,9 +35,11 @@ services: POSTGRES_PASSWORD: postgres pg_bouncer: - image: bitnami/pgbouncer:latest + image: bitnamilegacy/pgbouncer:latest ports: - 6453:6432 + depends_on: + - tenant_db environment: POSTGRESQL_USERNAME: postgres POSTGRESQL_HOST: tenant_db @@ -47,76 +48,92 @@ services: PGBOUNCER_IGNORE_STARTUP_PARAMETERS: "extra_float_digits, options" PGBOUNCER_STATS_USERS: postgres - supavisor: - image: supabase/supavisor:1.1.23 - depends_on: - multitenant_db: - condition: service_healthy - tenant_db: - condition: service_healthy + multitenant_pgbouncer: + image: bitnamilegacy/pgbouncer:latest ports: - - 4000:4000 - - 5452:5452 - - 6543:6543 - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:4000/api/health"] - interval: 2s - timeout: 10s - retries: 5 + - 6454:6432 + depends_on: + - multitenant_db environment: - PORT: 4000 - PROXY_PORT_SESSION: 5452 - PROXY_PORT_TRANSACTION: 6543 - DATABASE_URL: "ecto://postgres:postgres@multitenant_db:5432/postgres" - CLUSTER_POSTGRES: "true" - SECRET_KEY_BASE: "12345678901234567890121234567890123456789012345678903212345678901234567890123456789032123456789012345678901234567890323456789032" - VAULT_ENC_KEY: "12345678901234567890123456789032" - API_JWT_SECRET: "dev" - METRICS_JWT_SECRET: "dev" - REGION: "local" - ERL_AFLAGS: -proto_dist inet_tcp - command: sh -c "/app/bin/migrate && /app/bin/server" + POSTGRESQL_USERNAME: postgres + POSTGRESQL_HOST: multitenant_db + POSTGRESQL_PASSWORD: postgres + POSTGRESQL_DATABASE: postgres + POSTGRESQL_PORT: 5432 + PGBOUNCER_POOL_MODE: transaction + PGBOUNCER_IGNORE_STARTUP_PARAMETERS: "extra_float_digits, options, statement_timeout" + PGBOUNCER_STATS_USERS: postgres - supavisor_setup: - image: supabase/supavisor:1.1.23 - command: | - curl -X PUT \ - "http://supavisor:4000/api/tenants/bjhaohmqunupljrqypxz" \ - --header "Accept: application/json" \ - --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJhbm9uIiwiaWF0IjoxNjQ1MTkyODI0LCJleHAiOjE5NjA3Njg4MjR9.M9jrxyvPLkUxWgOYSf5dNdJ8v_eRrq810ShFRT8N-6M" \ - --header "Content-Type: application/json" \ - --data-raw "{ - \"tenant\": { - \"db_host\": \"tenant_db\", - \"db_port\": 5432, - \"db_database\": \"postgres\", - \"ip_version\": \"auto\", - \"require_user\": true, - \"upstream_ssl\": false, - \"enforce_ssl\": false, - \"default_max_clients\": 200, - \"default_pool_size\": 15, - \"users\": [ - { - \"db_user\": \"postgres\", - \"db_password\": \"postgres\", - \"mode_type\": \"transaction\", - \"pool_size\": 15, - \"max_clients\": 200, - \"pool_checkout_timeout\": 5000 - } - ] - } - }" - depends_on: - supavisor: - condition: service_healthy + # supavisor: + # image: supabase/supavisor:1.1.23 + # depends_on: + # multitenant_db: + # condition: service_healthy + # tenant_db: + # condition: service_healthy + # ports: + # - 4000:4000 + # - 5452:5452 + # - 6543:6543 + # healthcheck: + # test: ["CMD", "curl", "-f", "http://localhost:4000/api/health"] + # interval: 2s + # timeout: 10s + # retries: 5 + # environment: + # PORT: 4000 + # PROXY_PORT_SESSION: 5452 + # PROXY_PORT_TRANSACTION: 6543 + # DATABASE_URL: "ecto://postgres:postgres@multitenant_db:5432/postgres" + # CLUSTER_POSTGRES: "true" + # SECRET_KEY_BASE: "12345678901234567890121234567890123456789012345678903212345678901234567890123456789032123456789012345678901234567890323456789032" + # VAULT_ENC_KEY: "12345678901234567890123456789032" + # API_JWT_SECRET: "dev" + # METRICS_JWT_SECRET: "dev" + # REGION: "local" + # ERL_AFLAGS: -proto_dist inet_tcp + # command: sh -c "/app/bin/migrate && /app/bin/server" + # + # supavisor_setup: + # image: supabase/supavisor:1.1.23 + # command: | + # curl -X PUT \ + # "http://supavisor:4000/api/tenants/bjhaohmqunupljrqypxz" \ + # --header "Accept: application/json" \ + # --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJhbm9uIiwiaWF0IjoxNjQ1MTkyODI0LCJleHAiOjE5NjA3Njg4MjR9.M9jrxyvPLkUxWgOYSf5dNdJ8v_eRrq810ShFRT8N-6M" \ + # --header "Content-Type: application/json" \ + # --data-raw "{ + # \"tenant\": { + # \"db_host\": \"tenant_db\", + # \"db_port\": 5432, + # \"db_database\": \"postgres\", + # \"ip_version\": \"auto\", + # \"require_user\": true, + # \"upstream_ssl\": false, + # \"enforce_ssl\": false, + # \"default_max_clients\": 200, + # \"default_pool_size\": 15, + # \"users\": [ + # { + # \"db_user\": \"postgres\", + # \"db_password\": \"postgres\", + # \"mode_type\": \"transaction\", + # \"pool_size\": 15, + # \"max_clients\": 200, + # \"pool_checkout_timeout\": 5000 + # } + # ] + # } + # }" + # depends_on: + # supavisor: + # condition: service_healthy minio: image: minio/minio ports: - - '9000:9000' - - '9001:9001' + - "9000:9000" + - "9001:9001" networks: default: aliases: @@ -149,7 +166,7 @@ services: imgproxy: image: darthsim/imgproxy ports: - - '50020:8080' + - "50020:8080" volumes: - ${PWD}/data:${PWD}/data environment: @@ -177,25 +194,43 @@ services: - CATALOG_IO__IMPL=org.apache.iceberg.aws.s3.S3FileIO - CATALOG_S3_ENDPOINT=http://minio:9000 -# Optional for rate-limiting -# redis: -# image: redis:6.2-alpine -# restart: always -# ports: -# - '6379:6379' + # Optional for rate-limiting + # redis: + # image: redis:6.2-alpine + # restart: always + # ports: + # - '6379:6379' + + # Optional for tracing + otel: + profiles: + - monitoring + extends: + service: otel-collector + file: ./.docker/docker-compose-monitoring.yml + + jaeger: + profiles: + - monitoring + extends: + service: jaeger + file: ./.docker/docker-compose-monitoring.yml + + grafana: + profiles: + - monitoring + extends: + service: grafana + file: ./.docker/docker-compose-monitoring.yml -# Optional for tracing -# otel: -# extends: -# service: otel-collector -# file: ./.docker/docker-compose-monitoring.yml -# -# jaeger: -# extends: -# service: jaeger -# file: ./.docker/docker-compose-monitoring.yml + prometheus: + profiles: + - monitoring + extends: + service: prometheus + file: ./.docker/docker-compose-monitoring.yml configs: init.sql: content: | - CREATE SCHEMA IF NOT EXISTS _supavisor; \ No newline at end of file + CREATE SCHEMA IF NOT EXISTS _supavisor; diff --git a/.docker/docker-compose-monitoring.yml b/.docker/docker-compose-monitoring.yml index 2fcd4cfb5..1712edc17 100644 --- a/.docker/docker-compose-monitoring.yml +++ b/.docker/docker-compose-monitoring.yml @@ -24,7 +24,8 @@ services: image: prom/prometheus container_name: prometheus command: - - '--config.file=/etc/prometheus/prometheus.yml' + - "--config.file=/etc/prometheus/prometheus.yml" + - "--web.enable-remote-write-receiver" ports: - 9090:9090 restart: unless-stopped @@ -47,22 +48,23 @@ services: jaeger: image: jaegertracing/all-in-one:1.57.0 ports: - - "16686:16686" # Jaeger UI - - "14250:14250" # GRPC - - "14268:14268" # HTTP - - "14269:14269" # HTTP - - "6831:6831/udp" # UDP - - "6832:6832/udp" # UDP - - "5778:5778" # HTTP + - "16686:16686" # Jaeger UI + - "14250:14250" # GRPC + - "14268:14268" # HTTP + - "14269:14269" # HTTP + - "6831:6831/udp" # UDP + - "6832:6832/udp" # UDP + - "5778:5778" # HTTP otel-collector: image: otel/opentelemetry-collector-contrib:0.100.0 ports: - - "4317:4317" # OTLP gRPC receiver - - "4318:4318" # OTLP Http receiver - - "55680:55680" # OTLP HTTP receiver - command: [ "--config=/etc/otel/otel-collector-config.yml" ] + - "4317:4317" # OTLP gRPC receiver + - "4318:4318" # OTLP Http receiver + - "55680:55680" # OTLP HTTP receiver + - "9200:9200" # Prometheus metrics receiver + command: ["--config=/etc/otel/otel-collector-config.yml"] depends_on: - jaeger volumes: - - ../monitoring/otel/config:/etc/otel \ No newline at end of file + - ../monitoring/otel/config:/etc/otel diff --git a/.docker/pgbouncer/Dockerfile b/.docker/pgbouncer/Dockerfile new file mode 100644 index 000000000..4ffb63e1e --- /dev/null +++ b/.docker/pgbouncer/Dockerfile @@ -0,0 +1,17 @@ +FROM edoburu/pgbouncer:v1.25.1-p0 + +USER root +RUN apk add --no-cache bash + +COPY --chown=postgres:postgres pgbouncer.ini.template /etc/pgbouncer/pgbouncer.ini.template +COPY --chown=postgres:postgres entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +EXPOSE 6432 + +USER postgres + +HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=3 \ + CMD printf "SHOW VERSION;\n" | nc 127.0.0.1 6432 >/dev/null 2>&1 || exit 1 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/.docker/pgbouncer/README.md b/.docker/pgbouncer/README.md new file mode 100644 index 000000000..3a68eb73d --- /dev/null +++ b/.docker/pgbouncer/README.md @@ -0,0 +1,117 @@ +# PgBouncer for Multitenant Database + +Production-ready PgBouncer container that auto-tunes connection pool settings based on the AWS RDS instance type. + +## Quick Start + +```bash +# Build +docker build -t pgbouncer-mt .docker/pgbouncer/ + +# Run (local development) +docker run -p 6432:6432 \ + -e DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5432/postgres \ + pgbouncer-mt + +# Run (production with auto-tuning) +docker run -p 6432:6432 \ + -e DATABASE_URL=postgresql://user:pass@my-rds-instance.amazonaws.com:5432/mydb \ + -e AWS_DB_INSTANCE_TYPE=db.r6g.xlarge \ + -e DATABASE_SSL_ROOT_CERT="$(cat rds-ca.pem)" \ + pgbouncer-mt + +# Connect through PgBouncer +psql postgresql://user:pass@localhost:6432/mydb +``` + +## Auto-Tuning + +When `AWS_DB_INSTANCE_TYPE` is set, pool sizes are derived from the instance memory: + +1. Estimate RDS `max_connections`: `memory_bytes / 9531392` (capped at 5000) +2. `default_pool_size` = 75% of max_connections +3. `max_client_conn` = 10x default_pool_size +4. `min_pool_size` = 10% of default_pool_size +5. `reserve_pool_size` = 5% of default_pool_size + +| Instance | Memory | Est. max_conn | Pool Size | Max Clients | +| -------------- | ------ | ------------- | --------- | ----------- | +| db.t3.micro | 1 GB | 112 | 84 | 840 | +| db.t3.large | 8 GB | 901 | 675 | 6,750 | +| db.r6g.xlarge | 32 GB | 3,604 | 2,703 | 27,030 | +| db.r6g.4xlarge | 128 GB | 5,000 | 3,750 | 37,500 | + +When no instance type is set, conservative local defaults are used: `default_pool_size=20`, `max_client_conn=200`. + +### Supported Instance Families + +`db.t3`, `db.t4g`, `db.r5`, `db.r6g`, `db.r6i`, `db.r7g`, `db.m5`, `db.m6g`, `db.m6i`, `db.m7g` + +## TLS + +TLS mode can be configured in three ways (highest priority first): + +1. `PGBOUNCER_SERVER_TLS_MODE` env var +2. `sslmode` query parameter in `DATABASE_URL` +3. Auto-detect: `verify-full` when `DATABASE_SSL_ROOT_CERT` is set, `disable` otherwise + +### Encrypted without verification (`no-verify`) + +Use `?sslmode=no-verify` in the DSN to encrypt the connection without certificate verification. This is mapped to pgbouncer's `require` mode: + +```bash +docker run -p 6432:6432 \ + -e DATABASE_URL=postgresql://user:pass@rds-host:5432/mydb?sslmode=no-verify \ + pgbouncer-mt +``` + +### Full verification with CA certificate + +Set `DATABASE_SSL_ROOT_CERT` to the CA certificate content (PEM or base64-encoded): + +```bash +# From a PEM file +-e DATABASE_SSL_ROOT_CERT="$(cat rds-combined-ca-bundle.pem)" + +# Base64-encoded +-e DATABASE_SSL_ROOT_CERT="$(base64 < rds-combined-ca-bundle.pem)" +``` + +This automatically enables `verify-full` mode unless overridden by `sslmode` in the DSN or `PGBOUNCER_SERVER_TLS_MODE`. + +## Environment Variables + +| Variable | Required | Default | Description | +| ------------------------------ | -------- | ------------------------- | ------------------------------------------------------------------------------ | +| `DATABASE_URL` | Yes | — | PostgreSQL connection string | +| `AWS_DB_INSTANCE_TYPE` | No | — | RDS instance type for auto-tuning (e.g. `db.r6g.xlarge`) | +| `DATABASE_SSL_ROOT_CERT` | No | — | PEM or base64 CA cert; enables `verify-full` TLS | +| `PGBOUNCER_POOL_MODE` | No | `transaction` | Pooling mode | +| `PGBOUNCER_DEFAULT_POOL_SIZE` | No | auto | Max backend connections per database | +| `PGBOUNCER_MAX_CLIENT_CONN` | No | auto | Max client connections | +| `PGBOUNCER_MIN_POOL_SIZE` | No | auto | Min backend connections kept open | +| `PGBOUNCER_RESERVE_POOL_SIZE` | No | auto | Extra connections for burst traffic | +| `PGBOUNCER_MAX_DB_CONNECTIONS` | No | `0` (unlimited) | Hard cap on backend connections | +| `PGBOUNCER_SERVER_TLS_MODE` | No | `verify-full` / `disable` | TLS mode (`disable`, `allow`, `prefer`, `require`, `verify-ca`, `verify-full`) | +| `PGBOUNCER_AUTH_TYPE` | No | `scram-sha-256` | Authentication method | +| `PGBOUNCER_ADMIN_USERS` | No | DSN user | Users allowed to run admin commands | +| `PGBOUNCER_STATS_USERS` | No | DSN user | Users allowed to view stats | + +Any auto-calculated value can be overridden by setting the corresponding env var explicitly. + +## Health Check + +The container includes a built-in health check that probes port 6432 every 10 seconds. Use it with orchestrators: + +```yaml +# docker-compose example +services: + pgbouncer: + build: .docker/pgbouncer/ + ports: + - "6432:6432" + environment: + DATABASE_URL: postgresql://postgres:postgres@db:5432/postgres + depends_on: + - db +``` diff --git a/.docker/pgbouncer/entrypoint.sh b/.docker/pgbouncer/entrypoint.sh new file mode 100644 index 000000000..7a6ce35ab --- /dev/null +++ b/.docker/pgbouncer/entrypoint.sh @@ -0,0 +1,275 @@ +#!/usr/bin/env bash +set -euo pipefail + +# --- Parse DATABASE_URL --- +if [ -z "${DATABASE_URL:-}" ]; then + echo "ERROR: DATABASE_URL is required" >&2 + exit 1 +fi + +# Extract components from postgresql://user:password@host:port/dbname?params +proto="${DATABASE_URL%%://*}" +rest="${DATABASE_URL#*://}" + +userinfo="${rest%%@*}" +hostpart="${rest#*@}" +hostport="${hostpart%%/*}" +dbname_params="${hostpart#*/}" + +if [[ "$userinfo" == *:* ]]; then + DB_USER="${userinfo%%:*}" + DB_PASS="${userinfo#*:}" +else + echo "ERROR: DATABASE_URL must include user:password" >&2 + exit 1 +fi +DB_HOST="${hostport%%:*}" +DB_PORT="${hostport##*:}" +DB_NAME="${dbname_params%%\?*}" + +# Decode percent-encoded characters in URI components (RFC 3986) +urldecode() { + printf '%b' "${1//%/\\x}" +} +DB_USER="$(urldecode "$DB_USER")" +DB_PASS="$(urldecode "$DB_PASS")" +DB_NAME="$(urldecode "$DB_NAME")" + +# Parse query parameters (e.g. sslmode=no-verify) +DB_QUERY="" +if [[ "$dbname_params" == *"?"* ]]; then + DB_QUERY="${dbname_params#*\?}" +fi + +# Extract sslmode from query string +DSN_SSLMODE="" +if [ -n "$DB_QUERY" ]; then + DSN_SSLMODE=$(echo "$DB_QUERY" | tr '&' '\n' | grep '^sslmode=' | head -1 | cut -d= -f2) +fi + +# Default port if not specified +if [ "$DB_PORT" = "$DB_HOST" ]; then + DB_PORT="5432" +fi + +echo "pgbouncer: backend=${DB_HOST}:${DB_PORT} db=${DB_NAME} user=${DB_USER}" + +# --- AWS instance type → memory (GiB) mapping --- +get_instance_memory_gb() { + local instance_type="$1" + case "$instance_type" in + # T3 family + db.t3.micro) echo 1 ;; + db.t3.small) echo 2 ;; + db.t3.medium) echo 4 ;; + db.t3.large) echo 8 ;; + db.t3.xlarge) echo 16 ;; + db.t3.2xlarge) echo 32 ;; + # T4g family + db.t4g.micro) echo 1 ;; + db.t4g.small) echo 2 ;; + db.t4g.medium) echo 4 ;; + db.t4g.large) echo 8 ;; + db.t4g.xlarge) echo 16 ;; + db.t4g.2xlarge) echo 32 ;; + # R5 family + db.r5.large) echo 16 ;; + db.r5.xlarge) echo 32 ;; + db.r5.2xlarge) echo 64 ;; + db.r5.4xlarge) echo 128 ;; + db.r5.8xlarge) echo 256 ;; + db.r5.12xlarge) echo 384 ;; + db.r5.16xlarge) echo 512 ;; + db.r5.24xlarge) echo 768 ;; + # R6g family + db.r6g.large) echo 16 ;; + db.r6g.xlarge) echo 32 ;; + db.r6g.2xlarge) echo 64 ;; + db.r6g.4xlarge) echo 128 ;; + db.r6g.8xlarge) echo 256 ;; + db.r6g.12xlarge) echo 384 ;; + db.r6g.16xlarge) echo 512 ;; + # R6i family + db.r6i.large) echo 16 ;; + db.r6i.xlarge) echo 32 ;; + db.r6i.2xlarge) echo 64 ;; + db.r6i.4xlarge) echo 128 ;; + db.r6i.8xlarge) echo 256 ;; + db.r6i.12xlarge) echo 384 ;; + db.r6i.16xlarge) echo 512 ;; + # R7g family + db.r7g.large) echo 16 ;; + db.r7g.xlarge) echo 32 ;; + db.r7g.2xlarge) echo 64 ;; + db.r7g.4xlarge) echo 128 ;; + db.r7g.8xlarge) echo 256 ;; + db.r7g.12xlarge) echo 384 ;; + db.r7g.16xlarge) echo 512 ;; + # M5 family + db.m5.large) echo 8 ;; + db.m5.xlarge) echo 16 ;; + db.m5.2xlarge) echo 32 ;; + db.m5.4xlarge) echo 64 ;; + db.m5.8xlarge) echo 128 ;; + db.m5.12xlarge) echo 192 ;; + db.m5.16xlarge) echo 256 ;; + db.m5.24xlarge) echo 384 ;; + # M6g family + db.m6g.large) echo 8 ;; + db.m6g.xlarge) echo 16 ;; + db.m6g.2xlarge) echo 32 ;; + db.m6g.4xlarge) echo 64 ;; + db.m6g.8xlarge) echo 128 ;; + db.m6g.12xlarge) echo 192 ;; + db.m6g.16xlarge) echo 256 ;; + # M6i family + db.m6i.large) echo 8 ;; + db.m6i.xlarge) echo 16 ;; + db.m6i.2xlarge) echo 32 ;; + db.m6i.4xlarge) echo 64 ;; + db.m6i.8xlarge) echo 128 ;; + db.m6i.12xlarge) echo 192 ;; + db.m6i.16xlarge) echo 256 ;; + # M7g family + db.m7g.large) echo 8 ;; + db.m7g.xlarge) echo 16 ;; + db.m7g.2xlarge) echo 32 ;; + db.m7g.4xlarge) echo 64 ;; + db.m7g.8xlarge) echo 128 ;; + db.m7g.12xlarge) echo 192 ;; + db.m7g.16xlarge) echo 256 ;; + *) + echo "WARN: Unknown instance type '${instance_type}', using local defaults" >&2 + echo 0 + ;; + esac +} + +# --- Calculate pool settings from instance memory --- +calculate_pool_settings() { + local memory_gb="$1" + + if [ "$memory_gb" -eq 0 ]; then + # Local/unknown: use conservative defaults + DEFAULT_POOL_SIZE=20 + MAX_CLIENT_CONN=200 + MIN_POOL_SIZE=2 + RESERVE_POOL_SIZE=1 + MAX_DB_CONNECTIONS=0 + return + fi + + local memory_bytes=$((memory_gb * 1073741824)) + local rds_max_conn=$((memory_bytes / 9531392)) + + # Cap at 5000 (RDS hard limit for most instances) + if [ "$rds_max_conn" -gt 5000 ]; then + rds_max_conn=5000 + fi + + DEFAULT_POOL_SIZE=$((rds_max_conn * 75 / 100)) + MAX_CLIENT_CONN=$((DEFAULT_POOL_SIZE * 10)) + MIN_POOL_SIZE=$((DEFAULT_POOL_SIZE / 10)) + RESERVE_POOL_SIZE=$((DEFAULT_POOL_SIZE / 20)) + + # Floor values + [ "$MIN_POOL_SIZE" -lt 1 ] && MIN_POOL_SIZE=1 + [ "$RESERVE_POOL_SIZE" -lt 1 ] && RESERVE_POOL_SIZE=1 + + MAX_DB_CONNECTIONS=0 + + echo "pgbouncer: instance=${AWS_DB_INSTANCE_TYPE} memory=${memory_gb}GB rds_max_conn=${rds_max_conn}" +} + +# --- Compute settings --- +if [ -n "${AWS_DB_INSTANCE_TYPE:-}" ]; then + MEMORY_GB=$(get_instance_memory_gb "$AWS_DB_INSTANCE_TYPE") + calculate_pool_settings "$MEMORY_GB" +else + echo "pgbouncer: no AWS_DB_INSTANCE_TYPE set, using local defaults" + calculate_pool_settings 0 +fi + +# Allow env var overrides +DEFAULT_POOL_SIZE="${PGBOUNCER_DEFAULT_POOL_SIZE:-$DEFAULT_POOL_SIZE}" +MAX_CLIENT_CONN="${PGBOUNCER_MAX_CLIENT_CONN:-$MAX_CLIENT_CONN}" +MIN_POOL_SIZE="${PGBOUNCER_MIN_POOL_SIZE:-$MIN_POOL_SIZE}" +RESERVE_POOL_SIZE="${PGBOUNCER_RESERVE_POOL_SIZE:-$RESERVE_POOL_SIZE}" +MAX_DB_CONNECTIONS="${PGBOUNCER_MAX_DB_CONNECTIONS:-$MAX_DB_CONNECTIONS}" +POOL_MODE="${PGBOUNCER_POOL_MODE:-transaction}" +APP_NAME="${PGBOUNCER_APP_NAME:-Supabase Storage PgBouncer ${VERSION:-0.0.0}}" +AUTH_TYPE="${PGBOUNCER_AUTH_TYPE:-scram-sha-256}" +ADMIN_USERS="${PGBOUNCER_ADMIN_USERS:-$DB_USER}" +STATS_USERS="${PGBOUNCER_STATS_USERS:-$DB_USER}" + +echo "pgbouncer: pool_mode=${POOL_MODE} default_pool_size=${DEFAULT_POOL_SIZE} max_client_conn=${MAX_CLIENT_CONN} min_pool_size=${MIN_POOL_SIZE} reserve_pool_size=${RESERVE_POOL_SIZE}" + +# --- TLS configuration --- +# Priority: PGBOUNCER_SERVER_TLS_MODE env var > sslmode from DATABASE_URL > auto-detect +# Map non-standard sslmode values to pgbouncer equivalents: +# no-verify -> require (encrypted, skip certificate verification) +map_sslmode() { + case "$1" in + no-verify) echo "require" ;; + *) echo "$1" ;; + esac +} + +TLS_CONFIG="" +if [ -n "${DATABASE_SSL_ROOT_CERT:-}" ]; then + CERT_PATH="/etc/pgbouncer/ca.crt" + + # Detect if content is base64-encoded (no PEM header) + if echo "$DATABASE_SSL_ROOT_CERT" | grep -q "BEGIN CERTIFICATE"; then + echo "$DATABASE_SSL_ROOT_CERT" > "$CERT_PATH" + else + echo "$DATABASE_SSL_ROOT_CERT" | base64 -d > "$CERT_PATH" + fi + + TLS_MODE="${PGBOUNCER_SERVER_TLS_MODE:-${DSN_SSLMODE:-verify-full}}" + TLS_MODE=$(map_sslmode "$TLS_MODE") + TLS_CONFIG="server_tls_sslmode = ${TLS_MODE} +server_tls_ca_file = ${CERT_PATH}" + + echo "pgbouncer: TLS enabled, mode=${TLS_MODE}" +else + TLS_MODE="${PGBOUNCER_SERVER_TLS_MODE:-${DSN_SSLMODE:-disable}}" + TLS_MODE=$(map_sslmode "$TLS_MODE") + if [ "$TLS_MODE" != "disable" ]; then + TLS_CONFIG="server_tls_sslmode = ${TLS_MODE}" + echo "pgbouncer: TLS mode=${TLS_MODE} (no CA cert provided)" + fi +fi + +TLS_FILE="$(mktemp)" +printf '%s\n' "$TLS_CONFIG" > "$TLS_FILE" + +# --- Generate userlist.txt --- +printf '"%s" "%s"\n' "$DB_USER" "$DB_PASS" > /etc/pgbouncer/userlist.txt + +# --- Generate pgbouncer.ini from template --- +sed \ + -e "s|{{DB_HOST}}|${DB_HOST}|g" \ + -e "s|{{DB_PORT}}|${DB_PORT}|g" \ + -e "s|{{DB_NAME}}|${DB_NAME}|g" \ + -e "s|{{DB_USER}}|${DB_USER}|g" \ + -e "s|{{POOL_MODE}}|${POOL_MODE}|g" \ + -e "s|{{AUTH_TYPE}}|${AUTH_TYPE}|g" \ + -e "s|{{DEFAULT_POOL_SIZE}}|${DEFAULT_POOL_SIZE}|g" \ + -e "s|{{MIN_POOL_SIZE}}|${MIN_POOL_SIZE}|g" \ + -e "s|{{RESERVE_POOL_SIZE}}|${RESERVE_POOL_SIZE}|g" \ + -e "s|{{MAX_CLIENT_CONN}}|${MAX_CLIENT_CONN}|g" \ + -e "s|{{MAX_DB_CONNECTIONS}}|${MAX_DB_CONNECTIONS}|g" \ + -e "s|{{APP_NAME}}|${APP_NAME}|g" \ + -e "s|{{ADMIN_USERS}}|${ADMIN_USERS}|g" \ + -e "s|{{STATS_USERS}}|${STATS_USERS}|g" \ + -e "/{{TLS_CONFIG}}/{ + r ${TLS_FILE} + d + }" \ + /etc/pgbouncer/pgbouncer.ini.template > /etc/pgbouncer/pgbouncer.ini + +rm -f "$TLS_FILE" + +echo "pgbouncer: starting on port 6432" +exec pgbouncer /etc/pgbouncer/pgbouncer.ini diff --git a/.docker/pgbouncer/pgbouncer.ini.template b/.docker/pgbouncer/pgbouncer.ini.template new file mode 100644 index 000000000..d92f8b908 --- /dev/null +++ b/.docker/pgbouncer/pgbouncer.ini.template @@ -0,0 +1,35 @@ +[databases] +{{DB_NAME}} = host={{DB_HOST}} port={{DB_PORT}} dbname={{DB_NAME}} auth_user={{DB_USER}} application_name='{{APP_NAME}}' + +[pgbouncer] +listen_addr = 0.0.0.0 +listen_port = 6432 +auth_type = {{AUTH_TYPE}} +auth_file = /etc/pgbouncer/userlist.txt + +pool_mode = {{POOL_MODE}} +default_pool_size = {{DEFAULT_POOL_SIZE}} +min_pool_size = {{MIN_POOL_SIZE}} +reserve_pool_size = {{RESERVE_POOL_SIZE}} +reserve_pool_timeout = 3 +max_client_conn = {{MAX_CLIENT_CONN}} +max_db_connections = {{MAX_DB_CONNECTIONS}} + +server_lifetime = 3600 +server_idle_timeout = 600 +server_connect_timeout = 10 +server_login_retry = 1 +query_timeout = 0 +query_wait_timeout = 120 +client_idle_timeout = 0 +ignore_startup_parameters = extra_float_digits,statement_timeout,lock_timeout,idle_in_transaction_session_timeout,options + +log_connections = 0 +log_disconnections = 0 +log_pooler_errors = 1 +stats_period = 60 + +admin_users = {{ADMIN_USERS}} +stats_users = {{STATS_USERS}} + +{{TLS_CONFIG}} diff --git a/.env.sample b/.env.sample index 4af5f40b9..1816f6915 100644 --- a/.env.sample +++ b/.env.sample @@ -30,6 +30,7 @@ AUTH_JWT_ALGORITHM=HS256 ####################################### # MULTI_TENANT=true DATABASE_MULTITENANT_URL=postgresql://postgres:postgres@127.0.0.1:5433/postgres +DATABASE_MULTITENANT_POOL_URL=postgresql://postgres:postgres@127.0.0.1:6454/postgres REQUEST_X_FORWARDED_HOST_REGEXP=^([a-z]{20}).local.(?:com|dev)$ SERVER_ADMIN_API_KEYS=apikey AUTH_ENCRYPTION_KEY=encryptionkey @@ -43,6 +44,9 @@ DATABASE_POOL_URL=postgresql://postgres:postgres@127.0.0.1:6453/postgres DATABASE_CONNECTION_TIMEOUT=3000 DATABASE_SEARCH_PATH= +DATABASE_APPLICATION_NAME="Supabase Storage API" +PG_QUEUE_APPLICATION_NAME="Supabase Storage PgBoss" + ## When DATABASE_POOL_URL is SET the following params are ignored DATABASE_MAX_CONNECTIONS=20 DATABASE_FREE_POOL_AFTER_INACTIVITY=60000 @@ -147,8 +151,13 @@ WEBHOOK_API_KEY= ####################################### # Monitoring ####################################### -DEFAULT_METRICS_ENABLED=true LOG_LEVEL=info +OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4317 +OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:4317 +OTEL_METRICS_EXPORT_INTERVAL_MS=5000 +OTEL_METRICS_ENABLED=true +PROMETHEUS_METRICS_ENABLED=true + LOGFLARE_ENABLED=false LOGFLARE_API_KEY=api_key diff --git a/.env.test.sample b/.env.test.sample index fee2ccbb0..09e57619c 100644 --- a/.env.test.sample +++ b/.env.test.sample @@ -22,4 +22,12 @@ AWS_DEFAULT_REGION=ap-southeast-1 STORAGE_S3_ENDPOINT=http://127.0.0.1:9000 STORAGE_S3_PROTOCOL=http STORAGE_S3_FORCE_PATH_STYLE=true -REQUEST_X_FORWARDED_HOST_REGEXP= \ No newline at end of file +REQUEST_X_FORWARDED_HOST_REGEXP= + +VECTOR_ENABLED=true +VECTOR_S3_BUCKETS=supa-test-local-dev +ICEBERG_ENABLED=true +ICEBERG_BUCKET_DETECTION_MODE="BUCKET" + +OTEL_METRICS_ENABLED=false +PROMETHEUS_METRICS_ENABLED=false diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index bd385f7ab..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { - ignorePatterns: ['src/test/assets/**', 'src/test/db/**', 'src/test/*.yaml'], - parser: '@typescript-eslint/parser', - extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], - parserOptions: { - ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features - sourceType: 'module', // Allows for the use of imports - "project": "./tsconfig.json", - }, - rules: { - '@typescript-eslint/no-floating-promises': 'error', - '@typescript-eslint/no-explicit-any': 'warn', - '@typescript-eslint/no-unused-vars': [ - 'warn', - { 'argsIgnorePattern': '^_+$', 'varsIgnorePattern': '^_+$' } // allows intentionally unused variables named _ - ], - '@typescript-eslint/no-require-imports': 'warn', - }, -} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..1d330bf79 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @supabase/storage diff --git a/.github/actions/setup-node-npm/action.yml b/.github/actions/setup-node-npm/action.yml new file mode 100644 index 000000000..f5cbb51e4 --- /dev/null +++ b/.github/actions/setup-node-npm/action.yml @@ -0,0 +1,19 @@ +name: Setup Node and npm +description: Setup Node and pin npm from package.json packageManager + +inputs: + node-version: + description: Node.js version passed to actions/setup-node + default: "24" + +runs: + using: composite + steps: + - name: Set up Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: ${{ inputs.node-version }} + + - name: Pin npm + shell: bash + run: node scripts/ensure-npm-version.cjs diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..6d115c2f0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,21 @@ +version: 2 +updates: + # Enable version updates for npm + - package-ecosystem: "npm" + # Look for `package.json` and `lock` files in the `root` directory + directory: "/" + # Check for updates once a week + schedule: + interval: "weekly" + cooldown: + default-days: 3 + + # Enable version updates for Docker + - package-ecosystem: "docker" + # Look for a `Dockerfile` in the `root` directory + directory: "/" + # Check for updates once a week + schedule: + interval: "weekly" + cooldown: + default-days: 3 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a82c537b..2924a83d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,91 +10,190 @@ on: permissions: contents: read -jobs: - test: - name: Test / OS ${{ matrix.platform }} / Node ${{ matrix.node }} - strategy: - fail-fast: false - matrix: - platform: [ubuntu-24.04] - node: ['20'] - - runs-on: ${{ matrix.platform }} - timeout-minutes: 15 +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true +jobs: + lint_build: + name: Lint & Build + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 - - uses: actions/cache@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - - name: Set up Node - uses: actions/setup-node@v4 + - name: Set up Node and npm + uses: ./.github/actions/setup-node-npm with: - node-version: ${{ matrix.node }} - + node-version: "24" - name: Install dependencies - run: | - npm ci - - - name: Prettier checks - run: | - npm run lint + run: npm ci + - name: Lint + run: npm run lint + - name: Build + run: npm run build - - name: ESLint checks + test_unit: + name: Test / Unit + needs: lint_build + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 10 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + - name: Set up Node and npm + uses: ./.github/actions/setup-node-npm + with: + node-version: "24" + - name: Install dependencies + run: npm ci + - name: Prepare env files run: | - npm run eslint:check + cp .env.sample .env + cp .env.test.sample .env.test + - name: Unit tests pass + run: npm run test:unit:coverage + - name: Upload coverage results to Coveralls + uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + file: coverage/lcov.info + format: lcov + parallel: true + flag-name: unit + fail-on-error: false + continue-on-error: true - - name: Builds successfully + test_postgres: + name: Test / Postgres + needs: lint_build + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 30 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + - name: Set up Node and npm + uses: ./.github/actions/setup-node-npm + with: + node-version: "24" + - name: Install dependencies + run: npm ci + - name: Prepare env files run: | - npm run build - + cp .env.sample .env + cp .env.test.sample .env.test - name: Tests pass run: | - mkdir data && chmod -R 777 data && \ - npm run test:coverage - env: - ANON_KEY: ${{ secrets.ANON_KEY }} - SERVICE_KEY: ${{ secrets.SERVICE_KEY }} - TENANT_ID: ${{ secrets.TENANT_ID }} - REGION: ${{ secrets.REGION }} - GLOBAL_S3_BUCKET: ${{ secrets.GLOBAL_S3_BUCKET }} - PGRST_JWT_SECRET: ${{ secrets.PGRST_JWT_SECRET }} - AUTHENTICATED_KEY: ${{ secrets.AUTHENTICATED_KEY }} - DATABASE_URL: postgresql://postgres:postgres@127.0.0.1/postgres - FILE_SIZE_LIMIT: '52428800' - STORAGE_BACKEND: s3 - MULTITENANT_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5433/postgres - ADMIN_API_KEYS: apikey - ENABLE_IMAGE_TRANSFORMATION: true - IMGPROXY_URL: http://127.0.0.1:50020 - AWS_ACCESS_KEY_ID: supa-storage - AWS_SECRET_ACCESS_KEY: secret1234 - AWS_DEFAULT_REGION: ap-southeast-1 - GLOBAL_S3_ENDPOINT: http://127.0.0.1:9000 - GLOBAL_S3_PROTOCOL: http - GLOBAL_S3_FORCE_PATH_STYLE: true - DB_INSTALL_ROLES: true - ENABLE_DEFAULT_METRICS: false - PG_QUEUE_ENABLE: false - MULTI_TENANT: false - S3_PROTOCOL_ACCESS_KEY_ID: ${{ secrets.TENANT_ID }} - S3_PROTOCOL_ACCESS_KEY_SECRET: ${{ secrets.SERVICE_KEY }} - + mkdir -p data && chmod -R 777 data && \ + npm run test:coverage:ci - name: Upload coverage results to Coveralls - uses: coverallsapp/github-action@master + uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7 with: github-token: ${{ secrets.GITHUB_TOKEN }} + file: coverage/lcov.info + format: lcov + parallel: true + flag-name: rest + fail-on-error: false + continue-on-error: true + - name: Verify migration idempotency + env: &migration_idempotency_env + DATABASE_URL: postgresql://postgres:postgres@127.0.0.1/postgres + run: | + pg_dump "$DATABASE_URL" \ + --exclude-table-data=storage.migrations \ + --restrict-key=test \ + > before.sql + + npm run migration:test-idempotency + + pg_dump "$DATABASE_URL" \ + --exclude-table-data=storage.migrations \ + --restrict-key=test \ + > after.sql - - name: Ensure OrioleDB migration compatibility + diff before.sql after.sql || (echo 'Schema mismatch!'; exit 1) + + test_oriole: + name: Test / OrioleDB + needs: lint_build + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 30 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + - name: Set up Node and npm + uses: ./.github/actions/setup-node-npm + with: + node-version: "24" + - name: Install dependencies + run: npm ci + - name: Prepare env files run: | - npm run infra:restart:oriole - env: - PGRST_JWT_SECRET: ${{ secrets.PGRST_JWT_SECRET }} - DATABASE_URL: postgresql://postgres:postgres@127.0.0.1/postgres - DB_INSTALL_ROLES: true - ENABLE_DEFAULT_METRICS: false - PG_QUEUE_ENABLE: false - MULTI_TENANT: false \ No newline at end of file + cp .env.sample .env + cp .env.test.sample .env.test + - name: Tests pass with OrioleDB + run: | + mkdir -p data && chmod -R 777 data && \ + npm run test:oriole:ci + - name: Verify OrioleDB migration idempotency + env: *migration_idempotency_env + run: | + docker compose --project-directory . \ + -f ./.docker/docker-compose-infra.yml \ + -f ./.docker/docker-compose-infra-oriole-override.yml \ + exec -T tenant_db \ + pg_dump -U postgres -d postgres \ + --exclude-table-data=storage.migrations \ + --restrict-key=test \ + > before-oriole.sql + + npm run migration:test-idempotency + + docker compose --project-directory . \ + -f ./.docker/docker-compose-infra.yml \ + -f ./.docker/docker-compose-infra-oriole-override.yml \ + exec -T tenant_db \ + pg_dump -U postgres -d postgres \ + --exclude-table-data=storage.migrations \ + --restrict-key=test \ + > after-oriole.sql + + diff before-oriole.sql after-oriole.sql || (echo 'Oriole schema mismatch!'; exit 1) + + coveralls_finish: + name: Coveralls / Finalize + needs: + - test_unit + - test_postgres + if: ${{ always() }} + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 5 + steps: + - name: Finalize parallel Coveralls build + uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + parallel-finished: true + fail-on-error: false + continue-on-error: true diff --git a/.github/workflows/cli.yaml b/.github/workflows/cli.yaml index 25f766eb3..0d588a042 100644 --- a/.github/workflows/cli.yaml +++ b/.github/workflows/cli.yaml @@ -8,55 +8,73 @@ permissions: contents: read env: - service_to_update: 'storage-api' + service_to_update: "storage-api" cli_repo_owner: supabase cli_repo: cli - cli_repo_main_branch: 'main' - cli_repo_pr_title: 'fix(storage): update storage image version to ' - github_user: 'Supa CLI Bot' - github_user_email: 'fabri.feno@gmail.com' + cli_repo_main_branch: "main" + cli_repo_pr_title: "fix(storage): update storage image version to " + github_user: "Supa CLI Bot" + github_user_email: "cli_releaser_bot@supabase.com" jobs: update-remote-repo: runs-on: ubuntu-latest steps: + - name: Generate token + id: app-token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + with: + app-id: ${{ secrets.GH_CLI_RELEASER_APP_ID }} + private-key: ${{ secrets.GH_CLI_RELEASER_APP_PRIVATE_KEY }} + owner: ${{ env.cli_repo_owner }} + repositories: | + ${{ env.cli_repo }} - name: Setup Git config run: | git config --global user.name '${{ env.github_user }}' git config --global user.email '${{ env.github_user_email }}' - name: Checkout remote repository - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - repository: '${{ env.cli_repo_owner }}/${{ env.cli_repo }}' - token: ${{ secrets.CLI_PR_USER_ACCESS_TOKEN }} - path: '${{ env.cli_repo }}' + repository: "${{ env.cli_repo_owner }}/${{ env.cli_repo }}" + token: ${{ steps.app-token.outputs.token }} + path: "${{ env.cli_repo }}" - name: Create a new branch + env: + GH_REF: ${{ github.ref_name }} run: | cd '${{ env.cli_repo }}' - git checkout -b '${{ env.service_to_update }}/${{ github.ref_name }}' + git checkout -b "${{ env.service_to_update }}/${GH_REF}" - name: Update file + env: + GH_REF: ${{ github.ref_name }} run: | cd '${{ env.cli_repo }}' - sed -i 's/${{ env.cli_repo_owner }}\/${{ env.service_to_update }}:v[0-9]*\.[0-9]*\.[0-9]*/${{ env.cli_repo_owner }}\/${{ env.service_to_update }}:${{ github.ref_name }}/' internal/utils/misc.go + sed -i "s|${{ env.cli_repo_owner }}/${{ env.service_to_update }}:v[0-9]*\.[0-9]*\.[0-9]*|${{ env.cli_repo_owner }}/${{ env.service_to_update }}:${GH_REF}|" internal/utils/misc.go - name: Commit changes + env: + GH_REF: ${{ github.ref_name }} run: | cd '${{ env.cli_repo }}' git add . - git commit -m "Update ${{ env.service_to_update }} version to ${{ github.ref_name }}" + git commit -m "Update ${{ env.service_to_update }} version to ${GH_REF}" - name: Push changes + env: + GH_REF: ${{ github.ref_name }} run: | cd '${{ env.cli_repo }}' - git push origin '${{ env.service_to_update }}/${{ github.ref_name }}' + git push origin "${{ env.service_to_update }}/${GH_REF}" - name: Create Pull Request + env: + GH_REF: ${{ github.ref_name }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | cd '${{ env.cli_repo }}' - gh pr create --base '${{ env.cli_repo_main_branch }}' --title "${{ env.cli_repo_pr_title }} ${{ github.ref_name }}" --body "New version of ${{ env.service_to_update }} ${{ github.ref_name }} is now available!" - env: - GH_TOKEN: ${{ secrets.CLI_PR_USER_ACCESS_TOKEN }} \ No newline at end of file + gh pr create --base '${{ env.cli_repo_main_branch }}' --title "${{ env.cli_repo_pr_title }} ${GH_REF}" --body "New version of ${{ env.service_to_update }} ${GH_REF} is now available!" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ae3be4f28..b084cc051 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,20 +17,20 @@ jobs: fail-fast: false matrix: platform: [ubuntu-24.04] - node: ['20'] + node: ["24"] runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v4 - - uses: actions/cache@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - - name: Set up Node - uses: actions/setup-node@v4 + - name: Set up Node and npm + uses: ./.github/actions/setup-node-npm with: node-version: ${{ matrix.node }} @@ -49,19 +49,19 @@ jobs: AUTHENTICATED_KEY: ${{ secrets.AUTHENTICATED_KEY }} DATABASE_URL: postgresql://postgres:postgres@127.0.0.1/postgres PGOPTIONS: -c search_path=storage,public - FILE_SIZE_LIMIT: '52428800' + FILE_SIZE_LIMIT: "52428800" STORAGE_BACKEND: s3 ENABLE_IMAGE_TRANSFORMATION: true - name: Generate Swagger UI - uses: Legion2/swagger-ui-action@v1 + uses: Legion2/swagger-ui-action@eff65dc3f193f0a749872be82f74baa35be0797d # v1.3.0 with: output: swagger-ui spec-file: static/api.json github_token: ${{ secrets.GITHUB_TOKEN }} - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: swagger-ui diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index 44c0a1185..d172a28dd 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: version: - description: 'Image tag' + description: "Image tag" required: true type: string @@ -20,24 +20,24 @@ jobs: id-token: write steps: - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: configure aws credentials - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1 with: role-to-assume: ${{ secrets.PROD_AWS_ROLE }} - aws-region: 'us-east-1' - - uses: docker/login-action@v2 + aws-region: "us-east-1" + - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: public.ecr.aws - - uses: docker/login-action@v2 + - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - uses: akhilerm/tag-push-action@v2.1.0 + - uses: akhilerm/tag-push-action@f35ff2cb99d407368b5c727adbcc14a2ed81d509 # v2.2.0 with: src: docker.io/supabase/storage-api:${{ inputs.version }} dst: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cb315e9b5..ffedf95ad 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,12 +16,12 @@ jobs: published: ${{ steps.semantic.outputs.new_release_published }} version: ${{ steps.semantic.outputs.new_release_version }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Set up Node - uses: actions/setup-node@v4 + - name: Set up Node and npm + uses: ./.github/actions/setup-node-npm with: - node-version: 20 + node-version: "24" - name: Install dependencies run: | @@ -29,15 +29,29 @@ jobs: - name: Semantic Release id: semantic - uses: cycjimmy/semantic-release-action@v4.1.1 + uses: cycjimmy/semantic-release-action@b12c8f6015dc215fe37bc154d4ad456dd3833c90 # v6.0.0 with: semantic_version: 24.1.0 extra_plugins: | @semantic-release/commit-analyzer @semantic-release/release-notes-generator + @semantic-release/exec @semantic-release/github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ANON_KEY: ${{ secrets.ANON_KEY }} + SERVICE_KEY: ${{ secrets.SERVICE_KEY }} + TENANT_ID: ${{ secrets.TENANT_ID }} + REGION: ${{ secrets.REGION }} + POSTGREST_URL: ${{ secrets.POSTGREST_URL }} + GLOBAL_S3_BUCKET: ${{ secrets.GLOBAL_S3_BUCKET }} + PGRST_JWT_SECRET: ${{ secrets.PGRST_JWT_SECRET }} + AUTHENTICATED_KEY: ${{ secrets.AUTHENTICATED_KEY }} + DATABASE_URL: postgresql://postgres:postgres@127.0.0.1/postgres + PGOPTIONS: -c search_path=storage,public + FILE_SIZE_LIMIT: "52428800" + STORAGE_BACKEND: s3 + ENABLE_IMAGE_TRANSFORMATION: true publish: needs: @@ -50,7 +64,7 @@ jobs: id-token: write steps: - id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 with: images: | supabase/storage-api @@ -61,52 +75,52 @@ jobs: tags: | type=raw,value=v${{ needs.release.outputs.version }} - - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 with: platforms: amd64,arm64 - - uses: docker/setup-buildx-action@v2 + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: configure aws credentials - staging - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1 with: role-to-assume: ${{ secrets.DEV_AWS_ROLE }} - aws-region: 'us-east-1' + aws-region: "us-east-1" - name: Login to ECR account - staging - uses: docker/login-action@v2 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: 436098097459.dkr.ecr.us-east-1.amazonaws.com - name: configure aws credentials - prod - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1 with: role-to-assume: ${{ secrets.PROD_AWS_ROLE }} - aws-region: 'us-east-1' + aws-region: "us-east-1" - name: Login to ECR - prod - uses: docker/login-action@v2 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: public.ecr.aws - name: Login to ECR account - prod - uses: docker/login-action@v2 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: 646182064048.dkr.ecr.us-east-1.amazonaws.com - name: Login to GHCR - uses: docker/login-action@v2 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - uses: docker/build-push-action@v3 + - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 with: push: true platforms: linux/amd64,linux/arm64 @@ -116,6 +130,59 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max + publish_pgbouncer: + needs: + - release + if: needs.release.outputs.published == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - id: meta + uses: docker/metadata-action@818d4b7b91585d195f67373fd9cb0332e31a7175 # v4.6.0 + with: + images: | + 436098097459.dkr.ecr.us-east-1.amazonaws.com/storage-pgbouncer + 646182064048.dkr.ecr.us-east-1.amazonaws.com/storage-pgbouncer + tags: | + type=raw,value=v${{ needs.release.outputs.version }} + + - uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0 + + - name: configure aws credentials - staging + uses: aws-actions/configure-aws-credentials@67fbcbb121271f7775d2e7715933280b06314838 # v1.7.0 + with: + role-to-assume: ${{ secrets.DEV_AWS_ROLE }} + aws-region: "us-east-1" + + - name: Login to ECR account - staging + uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0 + with: + registry: 436098097459.dkr.ecr.us-east-1.amazonaws.com + + - name: configure aws credentials - prod + uses: aws-actions/configure-aws-credentials@67fbcbb121271f7775d2e7715933280b06314838 # v1.7.0 + with: + role-to-assume: ${{ secrets.PROD_AWS_ROLE }} + aws-region: "us-east-1" + + - name: Login to ECR account - prod + uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0 + with: + registry: 646182064048.dkr.ecr.us-east-1.amazonaws.com + + - uses: docker/build-push-action@1104d471370f9806843c095c1db02b5a90c5f8b6 # v3.3.1 + with: + push: true + context: .docker/pgbouncer + platforms: linux/amd64 + tags: ${{ steps.meta.outputs.tags }} + cache-from: type=gha,scope=pgbouncer + cache-to: type=gha,mode=max,scope=pgbouncer + mirror: runs-on: ubuntu-latest needs: @@ -127,28 +194,27 @@ jobs: id-token: write steps: - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: configure aws credentials - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1 with: role-to-assume: ${{ secrets.PROD_AWS_ROLE }} - aws-region: 'us-east-1' - - uses: docker/login-action@v2 + aws-region: "us-east-1" + - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: public.ecr.aws - - uses: docker/login-action@v2 + - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - uses: akhilerm/tag-push-action@v2.1.0 + - uses: akhilerm/tag-push-action@f35ff2cb99d407368b5c727adbcc14a2ed81d509 # v2.2.0 with: src: docker.io/supabase/storage-api:v${{ needs.release.outputs.version }} dst: | public.ecr.aws/supabase/storage-api:latest docker.io/supabase/storage-api:latest ghcr.io/supabase/storage-api:latest - diff --git a/.gitignore b/.gitignore index 7b565b162..d98e80c66 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,10 @@ dist/ .env.* !.*.sample static/api.json +static/api-admin.json data/ bin/ coverage/ -.idea/ \ No newline at end of file +.idea/ +src/scripts/*.py +.claude/ \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..08289deef --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +package-lock=true +engine-strict=true diff --git a/.prettierignore b/.prettierignore index b57f93ac9..ddc4c197a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,8 @@ +** +!**/ +!**/*.md +!**/*.yml +!**/*.yaml + node_modules package-lock.json -src/test/db/docker-compose.yml \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 8df6df775..28270a427 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,7 +1,11 @@ { - "trailingComma": "es5", - "tabWidth": 2, - "semi": false, - "singleQuote": true, - "printWidth": 100 + "overrides": [ + { + "files": ["**/*.md", "**/*.yml", "**/*.yaml"], + "options": { + "tabWidth": 2, + "printWidth": 100 + } + } + ] } diff --git a/.releaserc b/.releaserc index 3a5967300..5254650a2 100644 --- a/.releaserc +++ b/.releaserc @@ -3,10 +3,27 @@ "master" ], "plugins": [ - ["@semantic-release/commit-analyzer", { + [ + "@semantic-release/commit-analyzer", + { "preset": "conventionalcommits" - }], + } + ], "@semantic-release/release-notes-generator", - "@semantic-release/github" + [ + "@semantic-release/exec", + { + "prepareCmd": "VERSION=${nextRelease.version} npm run docs:export" + } + ], + [ + "@semantic-release/github", + { + "assets": [ + "static/api.json", + "static/api-admin.json" + ] + } + ] ] } diff --git a/Dockerfile b/Dockerfile index e2b36bbbe..4542fa744 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,15 @@ # Base stage for shared environment setup -FROM node:22-alpine3.21 AS base +FROM node:24-alpine3.23 AS base RUN apk add --no-cache g++ make python3 WORKDIR /app COPY package.json package-lock.json ./ +COPY scripts/ensure-npm-version.cjs ./scripts/ensure-npm-version.cjs +RUN node scripts/ensure-npm-version.cjs # Dependencies stage - install and cache all dependencies FROM base AS dependencies +# Copy patches before npm ci so postinstall can apply them +COPY patches ./patches RUN npm ci # Cache the installed node_modules for later stages RUN cp -R node_modules /node_modules_cache @@ -16,10 +20,11 @@ COPY --from=dependencies /node_modules_cache ./node_modules COPY . . RUN npm run build -# Production dependencies stage - use npm cache to install only production dependencies +# Production dependencies stage - prune dev dependencies from cached node_modules FROM base AS production-deps COPY --from=dependencies /node_modules_cache ./node_modules -RUN npm ci --production +# Use npm prune to remove dev dependencies while keeping compiled native modules and patches +RUN npm prune --omit=dev # Final stage - for the production build FROM base AS final @@ -31,6 +36,8 @@ COPY migrations migrations COPY --from=production-deps /app/node_modules node_modules # Copy build artifacts from the build stage COPY --from=build /app/dist dist +COPY --from=build /app/watt.json /app + EXPOSE 5000 -CMD ["node", "dist/start/server.js"] \ No newline at end of file +CMD ["node", "dist/start/server.js"] diff --git a/README.md b/README.md index 3ebef4590..869e5ad54 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -# Supabase Storage Engine +# Supabase Storage Engine [![Coverage Status](https://coveralls.io/repos/github/supabase/storage/badge.svg?branch=master)](https://coveralls.io/github/supabase/storage?branch=master) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/supabase/storage) -[![Coverage Status](https://coveralls.io/repos/github/supabase/storage-api/badge.svg?branch=master)](https://coveralls.io/github/supabase/storage-api?branch=master) - -A scalable, light-weight object storage service. +A scalable, lightweight object storage service. > Read [this post](https://supabase.io/blog/2021/03/30/supabase-storage) on why we decided to build a new object storage service. @@ -12,7 +10,6 @@ A scalable, light-weight object storage service. - Integrates with S3 Compatible Storages - Extremely lightweight and performant - **Supported Protocols** - [x] HTTP/REST @@ -20,7 +17,7 @@ A scalable, light-weight object storage service. - [x] S3 Compatible API - [x] Iceberg REST Catalog -![Architecture](./static/architecture.png?raw=true 'Architecture') +![Architecture](./static/architecture.png?raw=true "Architecture") ## Documentation @@ -35,7 +32,7 @@ A scalable, light-weight object storage service. ```bash cp .env.sample .env && cp .env.test.sample .env.test -```` +``` **Your root directory should now have both `.env` and `.env.test` files.** @@ -70,3 +67,7 @@ curl --location --request GET 'http://localhost:5000/bucket' \ To perform your tests you can run the following command: `npm test` +### Code Quality + +- Check: `npm run lint` +- Automatic fix if possible: `npm run lint:fix` diff --git a/babel.config.cjs b/babel.config.cjs deleted file mode 100644 index c74fb53e2..000000000 --- a/babel.config.cjs +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: [['@babel/preset-env', { targets: { node: 'current' } }]], -}; diff --git a/biome.jsonc b/biome.jsonc new file mode 100644 index 000000000..c9464dc7e --- /dev/null +++ b/biome.jsonc @@ -0,0 +1,67 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.4/schema.json", + "files": { + "ignoreUnknown": true, + "includes": [ + "**/*.ts", + "**/*.js", + "**/*.cjs", + "**/*.mjs", + "**/*.json", + "**/*.jsonc", + "!!package-lock.json", + "!!build/**", + "!!coverage/**", + "!!dist/**", + "!!static/**", + "!!data/**", + "!!node_modules/**", + "!!.claude/**" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": false, + "correctness": { + "noUnusedImports": "error", + "noUnusedVariables": "error" + }, + "style": { + "noCommonJs": "error", + "useConsistentObjectDefinitions": "error", + "useThrowOnlyError": "error" + }, + "suspicious": { + "noAssignInExpressions": "error", + "noAsyncPromiseExecutor": "error", + "noDoubleEquals": "error", + "noExplicitAny": "warn", + "noFocusedTests": "error", + "noImplicitAnyLet": "error", + "noShadowRestrictedNames": "error", + "noTsIgnore": "error" + }, + "nursery": { + "noFloatingPromises": "error", + "noUnnecessaryConditions": "error" + } + } + }, + "assist": { + "enabled": true + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "asNeeded", + "trailingCommas": "es5" + } + } +} diff --git a/build.js b/build.js index 8249c6e59..f00a62cfd 100644 --- a/build.js +++ b/build.js @@ -1,4 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires +// biome-ignore lint/style/noCommonJs: build script runs as CommonJS const { build } = require('esbuild') build({ @@ -7,7 +7,7 @@ build({ outdir: 'dist', platform: 'node', format: 'cjs', - target: 'node22', + target: 'node24', sourcemap: true, tsconfig: 'tsconfig.json', loader: { '.ts': 'ts' }, diff --git a/build.sh b/build.sh index 5db974cb6..524159edd 100755 --- a/build.sh +++ b/build.sh @@ -3,7 +3,7 @@ STORAGE_VERSION=$1 TYTONAI_VERSION=$2 -IMAGE_NAME=storage-api:v$STORAGE_VERSION-tytonai-$TYTONAI_VERSION +IMAGE_NAME=storage-api:v$TYTONAI_VERSION-supa-$STORAGE_VERSION HARBOR_PREFIX=${DOCKER_REGISTRY:-harbor.internal.millcrest.dev}/supabase GOOGLE_PREFIX=${GOOGLE_REGISTRY:-asia-southeast1-docker.pkg.dev/tytonai/docker}/supabase @@ -12,7 +12,6 @@ docker buildx build \ -t $HARBOR_PREFIX/$IMAGE_NAME \ -t $GOOGLE_PREFIX/$IMAGE_NAME \ --build-arg VERSION=v$STORAGE_VERSION \ - --platform linux/amd64 \ - --provenance=false \ - --output "type=image,compression=zstd,compression-level=15,oci-mediatypes=true,force-compression=true,push=true" \ - . \ No newline at end of file + --output "type=image,compression=zstd,push=true" \ + . + \ No newline at end of file diff --git a/docker-compose-multi-tenant.yml b/docker-compose-multi-tenant.yml index eb8faa684..2f66e74c2 100644 --- a/docker-compose-multi-tenant.yml +++ b/docker-compose-multi-tenant.yml @@ -1,21 +1,21 @@ # docker-compose.yml -version: '3' +version: "3" services: storage: image: supabase/storage-api:latest ports: - - '5000:5000' - - '5001:5001' + - "5000:5000" + - "5001:5001" depends_on: tenant_db: condition: service_healthy multitenant_db: condition: service_healthy supavisor: - condition: service_started + condition: service_started minio_setup: - condition: service_completed_successfully + condition: service_completed_successfully environment: # Server SERVER_PORT: 5000 @@ -34,7 +34,7 @@ services: DB_INSTALL_ROLES: true # set to false if you want to manage roles yourself # Storage STORAGE_BACKEND: s3 - STORAGE_S3_BUCKET: supa-storage-bucket # name of s3 bucket where you want to store objects + STORAGE_S3_BUCKET: supa-storage-bucket # name of s3 bucket where you want to store objects STORAGE_S3_ENDPOINT: http://minio:9000 STORAGE_S3_FORCE_PATH_STYLE: "true" STORAGE_S3_REGION: us-east-1 @@ -115,7 +115,6 @@ services: # service: postgres_exporter # file: ./.docker/docker-compose-monitoring.yml - configs: init.sql: content: "CREATE SCHEMA IF NOT EXISTS _supavisor;" diff --git a/docker-compose.yml b/docker-compose.yml index 4d944cc47..9bded87b1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,18 @@ # docker-compose.yml -version: '3' +version: "3" services: storage: image: supabase/storage-api:latest ports: - - '5000:5000' + - "5000:5000" depends_on: tenant_db: condition: service_healthy pg_bouncer: - condition: service_started + condition: service_started minio_setup: - condition: service_completed_successfully + condition: service_completed_successfully environment: # Server SERVER_PORT: 5000 @@ -26,7 +26,7 @@ services: DB_INSTALL_ROLES: true # set to false if you want to manage roles yourself # Storage STORAGE_BACKEND: s3 - STORAGE_S3_BUCKET: supa-storage-bucket # name of s3 bucket where you want to store objects + STORAGE_S3_BUCKET: supa-storage-bucket # name of s3 bucket where you want to store objects STORAGE_S3_ENDPOINT: http://minio:9000 STORAGE_S3_FORCE_PATH_STYLE: "true" STORAGE_S3_REGION: us-east-1 @@ -86,7 +86,6 @@ services: extends: service: rest-catalog file: ./.docker/docker-compose-infra.yml - # Optional for rate-limiting # redis: # extends: @@ -122,4 +121,4 @@ services: # jaeger: # extends: # service: jaeger -# file: ./.docker/docker-compose-monitoring.yml \ No newline at end of file +# file: ./.docker/docker-compose-monitoring.yml diff --git a/fly.toml b/fly.toml deleted file mode 100644 index 2ce20d4ee..000000000 --- a/fly.toml +++ /dev/null @@ -1,31 +0,0 @@ -# fly.toml file generated for storage-api on 2021-02-25T08:55:49+05:30 - -app = "storage-api" - -[build] - builtin = "node" - -kill_signal = "SIGINT" -kill_timeout = 5 - -[[services]] - internal_port = 8080 - protocol = "tcp" - - [services.concurrency] - hard_limit = 25 - soft_limit = 20 - - [[services.ports]] - handlers = ["http"] - port = "80" - - [[services.ports]] - handlers = ["tls", "http"] - port = "443" - - [[services.tcp_checks]] - grace_period = "1s" - interval = "10s" - restart_limit = 5 - timeout = "2s" diff --git a/jest-setup.ts b/jest-setup.ts deleted file mode 100644 index 4988cc381..000000000 --- a/jest-setup.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { getConfig, setEnvPaths } from './src/config' - -setEnvPaths(['.env.test', '.env']) - -beforeEach(() => { - getConfig({ reload: true }) -}) diff --git a/jest.config.cjs b/jest.config.cjs deleted file mode 100644 index 9cbfbd56c..000000000 --- a/jest.config.cjs +++ /dev/null @@ -1,21 +0,0 @@ -module.exports = { - preset: 'ts-jest', - testSequencer: './jest.sequencer.cjs', - transform: { - '^.+/node_modules/jose/.+\\.[jt]s$': 'babel-jest', - '^.+/node_modules/@tus/.+\\.[jt]s$': 'babel-jest', - '^.+/node_modules/srvx/.+\\.[jt]s$': 'babel-jest', - '^.+/node_modules/cookie-es/.+\\.[jt]s$': 'babel-jest', - '^.+\\.mjs$': 'babel-jest', - '^.+\\.(t|j)sx?$': 'ts-jest', - }, - transformIgnorePatterns: ['node_modules/(?!(jose|@tus|srvx|cookie-es)/)'], - moduleNameMapper: { - '^@storage/(.*)$': '/src/storage/$1', - '^@internal/(.*)$': '/src/internal/$1', - }, - setupFilesAfterEnv: ['/jest-setup.ts'], - testEnvironment: 'node', - testPathIgnorePatterns: ['node_modules', 'dist'], - coverageProvider: 'v8', -} diff --git a/jest.sequencer.cjs b/jest.sequencer.cjs deleted file mode 100644 index 786b0c971..000000000 --- a/jest.sequencer.cjs +++ /dev/null @@ -1,29 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires -const Sequencer = require('@jest/test-sequencer').default - -// order to sort tests based on name matching -const testOrder = [ - 'jwt.test.ts', // needs to run before it is imported by other libraries for proper coverage - '*', // all other tests not matched - 'tus.test.ts', - 's3-protocol.test.ts', - 'rls.test.ts', -] - -class CustomSequencer extends Sequencer { - sort(tests) { - const testBuckets = {} - testOrder.forEach(k => { - testBuckets[k] = [] - }) - - Array.from(tests).forEach(t => { - const fileName = t.path.split('/').pop() - const bucket = testBuckets[fileName] || testBuckets['*'] - bucket.push(t) - }) - return testOrder.flatMap(k => super.sort(testBuckets[k])) - } -} - -module.exports = CustomSequencer diff --git a/migrations/multitenant/0020-vector-buckets-feature.sql b/migrations/multitenant/0020-vector-buckets-feature.sql new file mode 100644 index 000000000..021f7d2e7 --- /dev/null +++ b/migrations/multitenant/0020-vector-buckets-feature.sql @@ -0,0 +1,3 @@ +ALTER TABLE tenants ADD COLUMN IF NOT EXISTS feature_vector_buckets boolean NOT NULL DEFAULT false; +ALTER TABLE tenants ADD COLUMN IF NOT EXISTS feature_vector_buckets_max_buckets int NOT NULL DEFAULT 10; +ALTER TABLE tenants ADD COLUMN IF NOT EXISTS feature_vector_buckets_max_indexes int NOT NULL DEFAULT 5; diff --git a/migrations/multitenant/0021-sharding-resources.sql b/migrations/multitenant/0021-sharding-resources.sql new file mode 100644 index 000000000..c1fee4797 --- /dev/null +++ b/migrations/multitenant/0021-sharding-resources.sql @@ -0,0 +1,78 @@ + + +-- Main shards table. +CREATE TABLE IF NOT EXISTS shard ( + id BIGSERIAL PRIMARY KEY, + kind TEXT NOT NULL, + shard_key TEXT NOT NULL, + capacity INT NOT NULL DEFAULT 10000, + next_slot INT NOT NULL DEFAULT 0, + status TEXT NOT NULL DEFAULT 'active', -- active|draining|disabled + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + UNIQUE (kind, shard_key) +); + +-- Sparse slot rows: only the slots that have ever been used exist here. +-- A "free" slot is a row with reservation_id NULL and resource_id NULL. +CREATE TABLE IF NOT EXISTS shard_slots ( + shard_id BIGINT NOT NULL REFERENCES shard(id) ON DELETE CASCADE, + slot_no INT NOT NULL, + tenant_id TEXT, + resource_id TEXT, -- set when confirmed + PRIMARY KEY (shard_id, slot_no) +); + +-- Reservations with short leases +CREATE TABLE IF NOT EXISTS shard_reservation ( + id UUID PRIMARY KEY default gen_random_uuid(), + kind text NOT NULL, + tenant_id TEXT, + resource_id TEXT NOT NULL, -- e.g. "vector::bucket::name" + shard_id BIGINT NOT NULL, + slot_no INT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', -- pending|confirmed|expired|cancelled + lease_expires_at TIMESTAMPTZ NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + UNIQUE (kind, resource_id), + UNIQUE (shard_id, slot_no) +); + +-- Fast “used count” per shard +CREATE INDEX IF NOT EXISTS shard_slots_used_idx + ON shard_slots (shard_id) + WHERE resource_id IS NOT NULL; + +ALTER TABLE shard + ADD CONSTRAINT shard_capacity_not_less_than_minted + CHECK (capacity >= next_slot); + + +-- Create index for counting slots by tenant +CREATE INDEX IF NOT EXISTS shard_slots_tenant_id_idx + ON shard_slots (tenant_id); + +-- Create index for counting reservations by tenant +CREATE INDEX IF NOT EXISTS shard_reservation_tenant_id_idx + ON shard_reservation (tenant_id); + +-- Create index for counting used slots by tenant +CREATE INDEX IF NOT EXISTS shard_slots_tenant_resource_idx + ON shard_slots (tenant_id, shard_id) + WHERE resource_id IS NOT NULL; + + +ALTER TABLE shard_reservation + ADD CONSTRAINT fk_shard_slot + FOREIGN KEY (shard_id, slot_no) + REFERENCES shard_slots(shard_id, slot_no) + ON DELETE RESTRICT; + + +CREATE INDEX IF NOT EXISTS shard_slots_free_idx + ON shard_slots (shard_id, slot_no) + WHERE resource_id IS NULL; + +-- Add index for finding active reservations by slot +CREATE INDEX IF NOT EXISTS shard_reservation_active_slot_idx + ON shard_reservation (shard_id, slot_no, lease_expires_at) + WHERE status = 'pending'; \ No newline at end of file diff --git a/migrations/multitenant/0022-iceberg-catalog-sharding.sql b/migrations/multitenant/0022-iceberg-catalog-sharding.sql new file mode 100644 index 000000000..2ed867151 --- /dev/null +++ b/migrations/multitenant/0022-iceberg-catalog-sharding.sql @@ -0,0 +1,39 @@ +DO $$ + DECLARE + iceberg_shards text[] = COALESCE(current_setting('storage.iceberg_shards', true), '[]::text[]')::text[]; + iceberg_default_shard text = COALESCE(current_setting('storage.iceberg_default_shard', true), '')::text; + i_shard_key text; + BEGIN + + ALTER TABLE iceberg_namespaces ADD COLUMN IF NOT EXISTS metadata JSONB NOT NULL DEFAULT '{}'; + + ALTER TABLE iceberg_tables ADD COLUMN IF NOT EXISTS remote_table_id TEXT NULL; + ALTER TABLE iceberg_tables ADD COLUMN IF NOT EXISTS shard_key TEXT NULL; + ALTER TABLE iceberg_tables ADD COLUMN IF NOT EXISTS shard_id bigint NULL; + + -- Only allow deleting namespaces if empty + ALTER TABLE iceberg_tables DROP CONSTRAINT IF EXISTS iceberg_tables_namespace_id_fkey; + ALTER TABLE iceberg_tables DROP CONSTRAINT IF EXISTS iceberg_tables_namespace_id_fkey; + + ALTER TABLE iceberg_tables + ADD CONSTRAINT iceberg_tables_namespace_id_fkey + FOREIGN KEY (namespace_id) + REFERENCES iceberg_namespaces(id) ON DELETE RESTRICT; + + IF array_length(iceberg_shards, 1) = 0 THEN + RETURN; + END IF; + + FOREACH i_shard_key IN ARRAY iceberg_shards + LOOP + INSERT INTO shard (kind, shard_key, capacity) VALUES ('iceberg-table', i_shard_key, 10000) + ON CONFLICT (kind, shard_key) DO NOTHING; + END LOOP; + + UPDATE iceberg_tables + SET shard_id = ( + SELECT id FROM shard WHERE kind = 'iceberg-table' AND shard_key = iceberg_default_shard LIMIT 1 + ), shard_key = iceberg_default_shard + WHERE shard_id IS NULL; + END +$$; diff --git a/migrations/multitenant/0023-iceberg-catalog-id.sql b/migrations/multitenant/0023-iceberg-catalog-id.sql new file mode 100644 index 000000000..804ac23f5 --- /dev/null +++ b/migrations/multitenant/0023-iceberg-catalog-id.sql @@ -0,0 +1,58 @@ +-- postgres-migrations disable-transaction +DO $$ + BEGIN + DROP INDEX IF EXISTS idx_iceberg_namespaces_bucket_id; + DROP INDEX IF EXISTS idx_iceberg_tables_tenant_namespace_id; + DROP INDEX IF EXISTS idx_iceberg_tables_tenant_location; + DROP INDEX IF EXISTS idx_iceberg_tables_location; + + -- remove primary key on iceberg_catalogs id + ALTER TABLE iceberg_catalogs DROP CONSTRAINT IF EXISTS iceberg_catalogs_pkey; + + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'iceberg_catalogs' AND column_name = 'name') THEN + ALTER TABLE iceberg_catalogs RENAME COLUMN id TO name; + END IF; + + ALTER TABLE iceberg_catalogs ADD COLUMN IF NOT EXISTS id uuid NOT NULL DEFAULT gen_random_uuid(); + ALTER TABLE iceberg_catalogs ADD PRIMARY KEY (id); + ALTER TABLE iceberg_catalogs ADD COLUMN IF NOT EXISTS deleted_at timestamptz NULL; + + CREATE INDEX IF NOT EXISTS iceberg_catalogs_unique_name_idx + ON iceberg_catalogs (tenant_id, name) WHERE deleted_at IS NULL; + + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'iceberg_namespaces' AND column_name = 'bucket_name') THEN + ALTER TABLE iceberg_namespaces RENAME COLUMN bucket_id to bucket_name; + END IF; + + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'iceberg_tables' AND column_name = 'bucket_name') THEN + ALTER TABLE iceberg_tables RENAME COLUMN bucket_id to bucket_name; + END IF; + + ALTER TABLE iceberg_namespaces ADD COLUMN IF NOT EXISTS catalog_id uuid NULL REFERENCES iceberg_catalogs(id) ON DELETE CASCADE ON UPDATE CASCADE ; + ALTER TABLE iceberg_tables ADD COLUMN IF NOT EXISTS catalog_id uuid NULL REFERENCES iceberg_catalogs(id) ON DELETE CASCADE ON UPDATE CASCADE; + + CREATE UNIQUE INDEX IF NOT EXISTS idx_iceberg_namespaces_bucket_id ON iceberg_namespaces (tenant_id, catalog_id, name); + CREATE UNIQUE INDEX IF NOT EXISTS idx_iceberg_tables_tenant_namespace_id ON iceberg_tables (tenant_id, namespace_id, catalog_id, name); + CREATE UNIQUE INDEX IF NOT EXISTS idx_iceberg_tables_tenant_location ON iceberg_tables (tenant_id, location); + CREATE UNIQUE INDEX IF NOT EXISTS idx_iceberg_tables_location ON iceberg_tables (location); + + -- create a unique index on name and deleted_at to allow only one active catalog with a given name + CREATE UNIQUE INDEX IF NOT EXISTS iceberg_catalogs_name_deleted_at_idx + ON iceberg_catalogs (tenant_id, name) + WHERE deleted_at IS NULL; + + -- Backfill catalog_id for existing namespaces and tables + UPDATE iceberg_tables it + SET catalog_id = c.id + FROM iceberg_catalogs c + WHERE c.name = it.bucket_name; + + UPDATE iceberg_namespaces iname + SET catalog_id = c.id + FROM iceberg_catalogs c + WHERE c.name = iname.bucket_name; + + ALTER TABLE iceberg_namespaces ALTER COLUMN catalog_id SET NOT NULL; + ALTER TABLE iceberg_tables ALTER COLUMN catalog_id SET NOT NULL; + END +$$; diff --git a/migrations/multitenant/0024-fixed-exactly-once-queue-index.sql b/migrations/multitenant/0024-fixed-exactly-once-queue-index.sql new file mode 100644 index 000000000..7fb1689a8 --- /dev/null +++ b/migrations/multitenant/0024-fixed-exactly-once-queue-index.sql @@ -0,0 +1,121 @@ +DO $$ + DECLARE + partition_queue_ids text[]; + i_partition_id text; + BEGIN + + -- check if a schema with name pgboss_v10 exists + IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'pgboss_v10') THEN + RETURN; + END IF; + + -- Create or replace function to archive exactly_once jobs + CREATE OR REPLACE FUNCTION pgboss_v10.archive_exactly_once_job() + RETURNS TRIGGER AS + $trigger$ + BEGIN + IF NEW.policy = 'exactly_once' AND NEW.state IN ('completed', 'failed', 'cancelled') THEN + INSERT INTO pgboss_v10.archive ( + id, name, priority, data, state, retry_limit, retry_count, retry_delay, retry_backoff, + start_after, started_on, singleton_key, singleton_on, expire_in, created_on, completed_on, + keep_until, output, dead_letter, policy + ) + VALUES ( + NEW.id, NEW.name, NEW.priority, NEW.data, NEW.state, NEW.retry_limit, NEW.retry_count, + NEW.retry_delay, NEW.retry_backoff, NEW.start_after, NEW.started_on, NEW.singleton_key, + NEW.singleton_on, NEW.expire_in, NEW.created_on, NEW.completed_on, NEW.keep_until + INTERVAL '30 days', + NEW.output, NEW.dead_letter, NEW.policy + ) + ON CONFLICT DO NOTHING; + + DELETE FROM pgboss_v10.job WHERE id = NEW.id; + END IF; + RETURN NEW; + END; + $trigger$ + LANGUAGE plpgsql; + + CREATE OR REPLACE FUNCTION pgboss_v10.create_queue(queue_name text, options json) + RETURNS VOID AS + $f$ + DECLARE + table_name varchar := 'j' || encode(sha224(queue_name::bytea), 'hex'); + queue_created_on timestamptz; + BEGIN + WITH q as ( + INSERT INTO pgboss_v10.queue ( + name, + policy, + retry_limit, + retry_delay, + retry_backoff, + expire_seconds, + retention_minutes, + dead_letter, + partition_name + ) + VALUES ( + queue_name, + options->>'policy', + (options->>'retryLimit')::int, + (options->>'retryDelay')::int, + (options->>'retryBackoff')::bool, + (options->>'expireInSeconds')::int, + (options->>'retentionMinutes')::int, + options->>'deadLetter', + table_name + ) + ON CONFLICT DO NOTHING + RETURNING created_on + ) + SELECT created_on into queue_created_on from q; + + IF queue_created_on IS NULL THEN + RETURN; + END IF; + + EXECUTE format('CREATE TABLE pgboss_v10.%I (LIKE pgboss_v10.job INCLUDING DEFAULTS)', table_name); + + EXECUTE format('ALTER TABLE pgboss_v10.%1$I ADD PRIMARY KEY (name, id)', table_name); + EXECUTE format('ALTER TABLE pgboss_v10.%1$I ADD CONSTRAINT q_fkey FOREIGN KEY (name) REFERENCES pgboss_v10.queue (name) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED', table_name); + EXECUTE format('ALTER TABLE pgboss_v10.%1$I ADD CONSTRAINT dlq_fkey FOREIGN KEY (dead_letter) REFERENCES pgboss_v10.queue (name) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED', table_name); + EXECUTE format('CREATE UNIQUE INDEX %1$s_i1 ON pgboss_v10.%1$I (name, COALESCE(singleton_key, '''')) WHERE state = ''created'' AND policy = ''short''', table_name); + EXECUTE format('CREATE UNIQUE INDEX %1$s_i2 ON pgboss_v10.%1$I (name, COALESCE(singleton_key, '''')) WHERE state = ''active'' AND policy = ''singleton''', table_name); + EXECUTE format('CREATE UNIQUE INDEX %1$s_i3 ON pgboss_v10.%1$I (name, state, COALESCE(singleton_key, '''')) WHERE state <= ''active'' AND policy = ''stately''', table_name); + EXECUTE format('CREATE UNIQUE INDEX %1$s_i4 ON pgboss_v10.%1$I (name, singleton_on, COALESCE(singleton_key, '''')) WHERE state <> ''cancelled'' AND singleton_on IS NOT NULL', table_name); + EXECUTE format('CREATE INDEX %1$s_i5 ON pgboss_v10.%1$I (name, start_after) INCLUDE (priority, created_on, id) WHERE state < ''active''', table_name); + EXECUTE format('CREATE UNIQUE INDEX %1$s_i6 ON pgboss_v10.%1$I (name, COALESCE(singleton_key, '''')) WHERE state <= ''active'' AND policy = ''exactly_once''', table_name); + + EXECUTE format('ALTER TABLE pgboss_v10.%I ADD CONSTRAINT cjc CHECK (name=%L)', table_name, queue_name); + EXECUTE format('ALTER TABLE pgboss_v10.job ATTACH PARTITION pgboss_v10.%I FOR VALUES IN (%L)', table_name, queue_name); + + -- create a function trigger to archive the job when it's exactly_once policy and the state is either completed, failed or cancelled + + EXECUTE format('CREATE TRIGGER archive_exactly_once_trigger_insert AFTER INSERT ON pgboss_v10.%I FOR EACH ROW EXECUTE FUNCTION pgboss_v10.archive_exactly_once_job()', table_name); + EXECUTE format('CREATE TRIGGER archive_exactly_once_trigger_update AFTER UPDATE ON pgboss_v10.%I FOR EACH ROW EXECUTE FUNCTION pgboss_v10.archive_exactly_once_job()', table_name); + END; + $f$ + LANGUAGE plpgsql; + + + + -- Recreate function with correct index type + SELECT array_agg(partition_name) from pgboss_v10.queue + WHERE policy = 'exactly_once' + INTO partition_queue_ids; + + IF array_length(partition_queue_ids, 1) = 0 THEN + RETURN; + END IF; + + FOR i_partition_id IN SELECT unnest(partition_queue_ids) + LOOP + EXECUTE format('DROP INDEX IF EXISTS pgboss_v10.%1$s_i6', i_partition_id); + EXECUTE format('CREATE UNIQUE INDEX IF NOT EXISTS %1$s_i6 ON pgboss_v10.%1$I (name, COALESCE(singleton_key, '''')) WHERE state <= ''active'' AND policy = ''exactly_once''', i_partition_id); + IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'archive_exactly_once_trigger_insert' AND tgrelid = ('pgboss_v10.' || i_partition_id)::regclass) THEN + EXECUTE format('CREATE TRIGGER archive_exactly_once_trigger_insert AFTER INSERT ON pgboss_v10.%I FOR EACH ROW EXECUTE FUNCTION pgboss_v10.archive_exactly_once_job()', i_partition_id); + EXECUTE format('CREATE TRIGGER archive_exactly_once_trigger_update AFTER UPDATE ON pgboss_v10.%I FOR EACH ROW EXECUTE FUNCTION pgboss_v10.archive_exactly_once_job()', i_partition_id); + END IF; + END LOOP; + END; +$$; \ No newline at end of file diff --git a/migrations/multitenant/0025-upgrade-from-event.sql b/migrations/multitenant/0025-upgrade-from-event.sql new file mode 100644 index 000000000..80b6e799e --- /dev/null +++ b/migrations/multitenant/0025-upgrade-from-event.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS event_upgrades ( + id SERIAL PRIMARY KEY, + event_id text NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(event_id) +); \ No newline at end of file diff --git a/migrations/multitenant/0026-improve-shard-reservation-partial-index.sql b/migrations/multitenant/0026-improve-shard-reservation-partial-index.sql new file mode 100644 index 000000000..d673e6499 --- /dev/null +++ b/migrations/multitenant/0026-improve-shard-reservation-partial-index.sql @@ -0,0 +1,9 @@ +ALTER TABLE shard_reservation +DROP CONSTRAINT IF EXISTS shard_reservation_kind_resource_id_key CASCADE; + +DROP INDEX IF EXISTS shard_reservation_active_slot_idx; + +-- Create partial unique index for confirmed reservations +-- Only one confirmed reservation per resource +CREATE UNIQUE INDEX IF NOT EXISTS shard_reservation_kind_resource_confirmed_idx + ON shard_reservation (tenant_id, kind, resource_id); \ No newline at end of file diff --git a/migrations/tenant/00010-search-files-search-function.sql b/migrations/tenant/00010-search-files-search-function.sql index ea61e67f5..fe5808469 100644 --- a/migrations/tenant/00010-search-files-search-function.sql +++ b/migrations/tenant/00010-search-files-search-function.sql @@ -1,4 +1,3 @@ -drop function storage.search; create or replace function storage.search ( prefix text, diff --git a/migrations/tenant/0002-storage-schema.sql b/migrations/tenant/0002-storage-schema.sql index d91586fea..59f0978d1 100644 --- a/migrations/tenant/0002-storage-schema.sql +++ b/migrations/tenant/0002-storage-schema.sql @@ -18,11 +18,23 @@ BEGIN END IF; -- Install ROLES - EXECUTE 'CREATE ROLE ' || anon_role || ' NOLOGIN NOINHERIT'; - EXECUTE 'CREATE ROLE ' || authenticated_role || ' NOLOGIN NOINHERIT'; - EXECUTE 'CREATE ROLE ' || service_role || ' NOLOGIN NOINHERIT bypassrls'; + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = anon_role) THEN + EXECUTE 'CREATE ROLE ' || anon_role || ' NOLOGIN NOINHERIT'; + END IF; + + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = authenticated_role ) THEN + EXECUTE 'CREATE ROLE ' || authenticated_role || ' NOLOGIN NOINHERIT'; + END IF; + + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = service_role) THEN + EXECUTE 'CREATE ROLE ' || service_role || ' NOLOGIN NOINHERIT bypassrls'; + END IF; + + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticator') THEN + EXECUTE 'CREATE USER authenticator NOINHERIT'; + END IF; + - create user authenticator noinherit; EXECUTE 'grant ' || anon_role || ' to authenticator'; EXECUTE 'grant ' || authenticated_role || ' to authenticator'; EXECUTE 'grant ' || service_role || ' to authenticator'; @@ -70,7 +82,6 @@ CREATE INDEX IF NOT EXISTS name_prefix_search ON storage.objects(name text_patte ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY; -drop function if exists storage.foldername; CREATE OR REPLACE FUNCTION storage.foldername(name text) RETURNS text[] LANGUAGE plpgsql @@ -83,7 +94,6 @@ BEGIN END $function$; -drop function if exists storage.filename; CREATE OR REPLACE FUNCTION storage.filename(name text) RETURNS text LANGUAGE plpgsql @@ -96,7 +106,6 @@ BEGIN END $function$; -drop function if exists storage.extension; CREATE OR REPLACE FUNCTION storage.extension(name text) RETURNS text LANGUAGE plpgsql @@ -113,7 +122,6 @@ END $function$; -- @todo can this query be optimised further? -drop function if exists storage.search; CREATE OR REPLACE FUNCTION storage.search(prefix text, bucketname text, limits int DEFAULT 100, levels int DEFAULT 1, offsets int DEFAULT 0) RETURNS TABLE ( name text, diff --git a/migrations/tenant/0006-change-column-name-in-get-size.sql b/migrations/tenant/0006-change-column-name-in-get-size.sql index f459cc1bc..a4f4836f3 100644 --- a/migrations/tenant/0006-change-column-name-in-get-size.sql +++ b/migrations/tenant/0006-change-column-name-in-get-size.sql @@ -1,4 +1,4 @@ -DROP FUNCTION storage.get_size_by_bucket(); +DROP FUNCTION IF EXISTS storage.get_size_by_bucket(); CREATE OR REPLACE FUNCTION storage.get_size_by_bucket() RETURNS TABLE ( size BIGINT, diff --git a/migrations/tenant/0009-fix-search-function.sql b/migrations/tenant/0009-fix-search-function.sql index 3d6fadc1d..f68c91718 100644 --- a/migrations/tenant/0009-fix-search-function.sql +++ b/migrations/tenant/0009-fix-search-function.sql @@ -1,4 +1,3 @@ -drop function if exists storage.search; CREATE OR REPLACE FUNCTION storage.search(prefix text, bucketname text, limits int DEFAULT 100, levels int DEFAULT 1, offsets int DEFAULT 0) RETURNS TABLE ( name text, diff --git a/migrations/tenant/0026-objects-prefixes.sql b/migrations/tenant/0026-objects-prefixes.sql index c71caf9cc..97cbeaee7 100644 --- a/migrations/tenant/0026-objects-prefixes.sql +++ b/migrations/tenant/0026-objects-prefixes.sql @@ -1,3 +1,4 @@ +-- postgres-migrations ignore -- Add level column to objects ALTER TABLE storage.objects ADD COLUMN IF NOT EXISTS level INT NULL; diff --git a/migrations/tenant/0027-search-v2.sql b/migrations/tenant/0027-search-v2.sql index a0521bdd1..9e74d60d8 100644 --- a/migrations/tenant/0027-search-v2.sql +++ b/migrations/tenant/0027-search-v2.sql @@ -1,4 +1,4 @@ - +-- postgres-migrations ignore CREATE OR REPLACE FUNCTION storage.search_v2 ( prefix text, bucket_name text, diff --git a/migrations/tenant/0028-object-bucket-name-sorting.sql b/migrations/tenant/0028-object-bucket-name-sorting.sql index 2cc0e86a1..cca3493c1 100644 --- a/migrations/tenant/0028-object-bucket-name-sorting.sql +++ b/migrations/tenant/0028-object-bucket-name-sorting.sql @@ -1,2 +1,3 @@ -- postgres-migrations disable-transaction +-- postgres-migrations ignore CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS idx_name_bucket_level_unique on storage.objects (name COLLATE "C", bucket_id, level); diff --git a/migrations/tenant/0029-create-prefixes.sql b/migrations/tenant/0029-create-prefixes.sql index 09380c49f..8ddff8b13 100644 --- a/migrations/tenant/0029-create-prefixes.sql +++ b/migrations/tenant/0029-create-prefixes.sql @@ -1,4 +1,5 @@ -- postgres-migrations disable-transaction +-- postgres-migrations ignore -- Backfill prefixes table records -- We run this with 50k batch size to avoid long running transaction DO $$ diff --git a/migrations/tenant/0030-update-object-levels.sql b/migrations/tenant/0030-update-object-levels.sql index 49d83dbf5..ffcb21384 100644 --- a/migrations/tenant/0030-update-object-levels.sql +++ b/migrations/tenant/0030-update-object-levels.sql @@ -1,4 +1,5 @@ -- postgres-migrations disable-transaction +-- postgres-migrations ignore -- Backfill prefixes table records -- We run this with 10k batch size to avoid long running transaction DO $$ diff --git a/migrations/tenant/0031-objects-level-index.sql b/migrations/tenant/0031-objects-level-index.sql index 8650e58ff..af81ba3e6 100644 --- a/migrations/tenant/0031-objects-level-index.sql +++ b/migrations/tenant/0031-objects-level-index.sql @@ -1,3 +1,4 @@ -- postgres-migrations disable-transaction +-- postgres-migrations ignore CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS "objects_bucket_id_level_idx" ON "storage"."objects" ("bucket_id", level, "name" COLLATE "C"); diff --git a/migrations/tenant/0032-backward-compatible-index-on-objects.sql b/migrations/tenant/0032-backward-compatible-index-on-objects.sql index 00989a160..4e042ab44 100644 --- a/migrations/tenant/0032-backward-compatible-index-on-objects.sql +++ b/migrations/tenant/0032-backward-compatible-index-on-objects.sql @@ -1,2 +1,3 @@ -- postgres-migrations disable-transaction +-- postgres-migrations ignore CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_objects_lower_name ON storage.objects ((path_tokens[level]), lower(name) text_pattern_ops, bucket_id, level); diff --git a/migrations/tenant/0033-backward-compatible-index-on-prefixes.sql b/migrations/tenant/0033-backward-compatible-index-on-prefixes.sql index f9cd624fe..35d1c7184 100644 --- a/migrations/tenant/0033-backward-compatible-index-on-prefixes.sql +++ b/migrations/tenant/0033-backward-compatible-index-on-prefixes.sql @@ -1,2 +1,3 @@ -- postgres-migrations disable-transaction +-- postgres-migrations ignore CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_prefixes_lower_name ON storage.prefixes (bucket_id, level, ((string_to_array(name, '/'))[level]), lower(name) text_pattern_ops); diff --git a/migrations/tenant/0034-optimize-search-function-v1.sql b/migrations/tenant/0034-optimize-search-function-v1.sql index 9b6646590..9c5e3486c 100644 --- a/migrations/tenant/0034-optimize-search-function-v1.sql +++ b/migrations/tenant/0034-optimize-search-function-v1.sql @@ -1,3 +1,4 @@ +-- postgres-migrations ignore create or replace function storage.search_v1_optimised ( prefix text, bucketname text, diff --git a/migrations/tenant/0035-add-insert-trigger-prefixes.sql b/migrations/tenant/0035-add-insert-trigger-prefixes.sql index 5f573effb..801bb0d4b 100644 --- a/migrations/tenant/0035-add-insert-trigger-prefixes.sql +++ b/migrations/tenant/0035-add-insert-trigger-prefixes.sql @@ -1,4 +1,4 @@ - +-- postgres-migrations ignore -- This trigger is used to create the hierarchy of prefixes -- When writing directly in the prefixes table CREATE OR REPLACE TRIGGER "prefixes_create_hierarchy" diff --git a/migrations/tenant/0036-optimise-existing-functions.sql b/migrations/tenant/0036-optimise-existing-functions.sql index 8eb9d0ac8..789922a3a 100644 --- a/migrations/tenant/0036-optimise-existing-functions.sql +++ b/migrations/tenant/0036-optimise-existing-functions.sql @@ -1,3 +1,4 @@ +-- postgres-migrations ignore create or replace function storage.search ( prefix text, bucketname text, diff --git a/migrations/tenant/0038-iceberg-catalog-flag-on-buckets.sql b/migrations/tenant/0038-iceberg-catalog-flag-on-buckets.sql index c21598aea..072319575 100644 --- a/migrations/tenant/0038-iceberg-catalog-flag-on-buckets.sql +++ b/migrations/tenant/0038-iceberg-catalog-flag-on-buckets.sql @@ -40,7 +40,9 @@ DO $$ updated_at timestamptz NOT NULL default now() ); - CREATE UNIQUE INDEX IF NOT EXISTS idx_iceberg_namespaces_bucket_id ON storage.iceberg_namespaces (bucket_id, name); + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'iceberg_namespaces' AND column_name = 'bucket_id') THEN + CREATE UNIQUE INDEX IF NOT EXISTS idx_iceberg_namespaces_bucket_id ON storage.iceberg_namespaces (bucket_id, name); + END IF; CREATE TABLE IF NOT EXISTS storage.iceberg_tables ( id uuid primary key default gen_random_uuid(), @@ -52,6 +54,7 @@ DO $$ updated_at timestamptz NOT NULL default now() ); + DROP INDEX IF EXISTS idx_iceberg_tables_namespace_id; CREATE UNIQUE INDEX idx_iceberg_tables_namespace_id ON storage.iceberg_tables (namespace_id, name); ALTER TABLE storage.iceberg_namespaces ENABLE ROW LEVEL SECURITY; diff --git a/migrations/tenant/0039-add-search-v2-sort-support.sql b/migrations/tenant/0039-add-search-v2-sort-support.sql new file mode 100644 index 000000000..16f4fdc73 --- /dev/null +++ b/migrations/tenant/0039-add-search-v2-sort-support.sql @@ -0,0 +1,108 @@ +-- postgres-migrations ignore +CREATE OR REPLACE FUNCTION storage.search_v2 ( + prefix text, + bucket_name text, + limits int DEFAULT 100, + levels int DEFAULT 1, + start_after text DEFAULT '', + sort_order text DEFAULT 'asc', + sort_column text DEFAULT 'name', + sort_column_after text DEFAULT '' +) RETURNS TABLE ( + key text, + name text, + id uuid, + updated_at timestamptz, + created_at timestamptz, + last_accessed_at timestamptz, + metadata jsonb +) +SECURITY INVOKER +AS $func$ +DECLARE + sort_col text; + sort_ord text; + cursor_op text; + cursor_expr text; + sort_expr text; +BEGIN + -- Validate sort_order + sort_ord := lower(sort_order); + IF sort_ord NOT IN ('asc', 'desc') THEN + sort_ord := 'asc'; + END IF; + + -- Determine cursor comparison operator + IF sort_ord = 'asc' THEN + cursor_op := '>'; + ELSE + cursor_op := '<'; + END IF; + + sort_col := lower(sort_column); + -- Validate sort column + IF sort_col IN ('updated_at', 'created_at') THEN + cursor_expr := format( + '($5 = '''' OR ROW(date_trunc(''milliseconds'', %I), name COLLATE "C") %s ROW(COALESCE(NULLIF($6, '''')::timestamptz, ''epoch''::timestamptz), $5))', + sort_col, cursor_op + ); + sort_expr := format( + 'COALESCE(date_trunc(''milliseconds'', %I), ''epoch''::timestamptz) %s, name COLLATE "C" %s', + sort_col, sort_ord, sort_ord + ); + ELSE + cursor_expr := format('($5 = '''' OR name COLLATE "C" %s $5)', cursor_op); + sort_expr := format('name COLLATE "C" %s', sort_ord); + END IF; + + RETURN QUERY EXECUTE format( + $sql$ + SELECT * FROM ( + ( + SELECT + split_part(name, '/', $4) AS key, + name, + NULL::uuid AS id, + updated_at, + created_at, + NULL::timestamptz AS last_accessed_at, + NULL::jsonb AS metadata + FROM storage.prefixes + WHERE name COLLATE "C" LIKE $1 || '%%' + AND bucket_id = $2 + AND level = $4 + AND %s + ORDER BY %s + LIMIT $3 + ) + UNION ALL + ( + SELECT + split_part(name, '/', $4) AS key, + name, + id, + updated_at, + created_at, + last_accessed_at, + metadata + FROM storage.objects + WHERE name COLLATE "C" LIKE $1 || '%%' + AND bucket_id = $2 + AND level = $4 + AND %s + ORDER BY %s + LIMIT $3 + ) + ) obj + ORDER BY %s + LIMIT $3 + $sql$, + cursor_expr, -- prefixes WHERE + sort_expr, -- prefixes ORDER BY + cursor_expr, -- objects WHERE + sort_expr, -- objects ORDER BY + sort_expr -- final ORDER BY + ) + USING prefix, bucket_name, limits, levels, start_after, sort_column_after; +END; +$func$ LANGUAGE plpgsql STABLE; diff --git a/migrations/tenant/0040-fix-prefix-race-conditions-optimized.sql b/migrations/tenant/0040-fix-prefix-race-conditions-optimized.sql new file mode 100644 index 000000000..4692f5e2a --- /dev/null +++ b/migrations/tenant/0040-fix-prefix-race-conditions-optimized.sql @@ -0,0 +1,265 @@ +-- postgres-migrations ignore +-- Drop old prefix-related triggers that conflict with new GC system +DROP TRIGGER IF EXISTS prefixes_delete_hierarchy ON storage.prefixes; +DROP TRIGGER IF EXISTS objects_delete_delete_prefix ON storage.objects; +DROP TRIGGER IF EXISTS objects_update_create_prefix ON storage.objects; + +-- Helper: Acquire statement-scoped advisory locks for the top-level path +-- for each \[bucket_id, name] pair to serialize operations per "bucket/top_level_prefix". +CREATE OR REPLACE FUNCTION storage.lock_top_prefixes(bucket_ids text[], names text[]) + RETURNS void + LANGUAGE plpgsql + SECURITY DEFINER +AS $$ +DECLARE + v_bucket text; + v_top text; +BEGIN + FOR v_bucket, v_top IN + SELECT DISTINCT t.bucket_id, + split_part(t.name, '/', 1) AS top + FROM unnest(bucket_ids, names) AS t(bucket_id, name) + WHERE t.name <> '' + ORDER BY 1, 2 + LOOP + PERFORM pg_advisory_xact_lock(hashtextextended(v_bucket || '/' || v_top, 0)); + END LOOP; +END; +$$; + +-- Helper: Given arrays of bucket_ids and names, compute all ancestor +-- prefixes and delete those that are leaves (no children objects or prefixes). +-- Repeats bottom-up until no more rows are removed. +CREATE OR REPLACE FUNCTION storage.delete_leaf_prefixes(bucket_ids text[], names text[]) + RETURNS void + LANGUAGE plpgsql + SECURITY DEFINER +AS $$ +DECLARE + v_rows_deleted integer; +BEGIN + LOOP + WITH candidates AS ( + SELECT DISTINCT t.bucket_id, + unnest(storage.get_prefixes(t.name)) AS name + FROM unnest(bucket_ids, names) AS t(bucket_id, name) + ), + uniq AS ( + SELECT bucket_id, + name, + storage.get_level(name) AS level + FROM candidates + WHERE name <> '' + GROUP BY bucket_id, name + ), + leaf AS ( + SELECT p.bucket_id, p.name, p.level + FROM storage.prefixes AS p + JOIN uniq AS u + ON u.bucket_id = p.bucket_id + AND u.name = p.name + AND u.level = p.level + WHERE NOT EXISTS ( + SELECT 1 + FROM storage.objects AS o + WHERE o.bucket_id = p.bucket_id + AND storage.get_level(o.name) = p.level + 1 + AND o.name COLLATE "C" LIKE p.name || '/%' + ) + AND NOT EXISTS ( + SELECT 1 + FROM storage.prefixes AS c + WHERE c.bucket_id = p.bucket_id + AND c.level = p.level + 1 + AND c.name COLLATE "C" LIKE p.name || '/%' + ) + ) + DELETE FROM storage.prefixes AS p + USING leaf AS l + WHERE p.bucket_id = l.bucket_id + AND p.name = l.name + AND p.level = l.level; + + GET DIAGNOSTICS v_rows_deleted = ROW_COUNT; + EXIT WHEN v_rows_deleted = 0; + END LOOP; +END; +$$; + +-- After DELETE on storage.objects +-- - Guards with `gc.prefixes` +-- - Locks top-level prefixes for touched objects +-- - Deletes leaf prefixes derived from deleted object names and their ancestors +CREATE OR REPLACE FUNCTION storage.objects_delete_cleanup() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER +AS $$ +DECLARE + v_bucket_ids text[]; + v_names text[]; +BEGIN + IF current_setting('storage.gc.prefixes', true) = '1' THEN + RETURN NULL; + END IF; + + PERFORM set_config('storage.gc.prefixes', '1', true); + + SELECT COALESCE(array_agg(d.bucket_id), '{}'), + COALESCE(array_agg(d.name), '{}') + INTO v_bucket_ids, v_names + FROM deleted AS d + WHERE d.name <> ''; + + PERFORM storage.lock_top_prefixes(v_bucket_ids, v_names); + PERFORM storage.delete_leaf_prefixes(v_bucket_ids, v_names); + + RETURN NULL; +END; +$$; + +-- After UPDATE on storage.objects +-- - Only OLD names matter for cleanup; NEW prefixes are created elsewhere +-- - Guards with `gc.prefixes`, locks, then prunes leaves derived from OLD names +CREATE OR REPLACE FUNCTION storage.objects_update_cleanup() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER +AS $$ +DECLARE + -- NEW - OLD (destinations to create prefixes for) + v_add_bucket_ids text[]; + v_add_names text[]; + + -- OLD - NEW (sources to prune) + v_src_bucket_ids text[]; + v_src_names text[]; +BEGIN + IF TG_OP <> 'UPDATE' THEN + RETURN NULL; + END IF; + + -- 1) Compute NEW−OLD (added paths) and OLD−NEW (moved-away paths) + WITH added AS ( + SELECT n.bucket_id, n.name + FROM new_rows n + WHERE n.name <> '' AND position('/' in n.name) > 0 + EXCEPT + SELECT o.bucket_id, o.name FROM old_rows o WHERE o.name <> '' + ), + moved AS ( + SELECT o.bucket_id, o.name + FROM old_rows o + WHERE o.name <> '' + EXCEPT + SELECT n.bucket_id, n.name FROM new_rows n WHERE n.name <> '' + ) + SELECT + -- arrays for ADDED (dest) in stable order + COALESCE( (SELECT array_agg(a.bucket_id ORDER BY a.bucket_id, a.name) FROM added a), '{}' ), + COALESCE( (SELECT array_agg(a.name ORDER BY a.bucket_id, a.name) FROM added a), '{}' ), + -- arrays for MOVED (src) in stable order + COALESCE( (SELECT array_agg(m.bucket_id ORDER BY m.bucket_id, m.name) FROM moved m), '{}' ), + COALESCE( (SELECT array_agg(m.name ORDER BY m.bucket_id, m.name) FROM moved m), '{}' ) + INTO v_add_bucket_ids, v_add_names, v_src_bucket_ids, v_src_names; + + -- Nothing to do? + IF (array_length(v_add_bucket_ids, 1) IS NULL) AND (array_length(v_src_bucket_ids, 1) IS NULL) THEN + RETURN NULL; + END IF; + + -- 2) Take per-(bucket, top) locks: ALL prefixes in consistent global order to prevent deadlocks + DECLARE + v_all_bucket_ids text[]; + v_all_names text[]; + BEGIN + -- Combine source and destination arrays for consistent lock ordering + v_all_bucket_ids := COALESCE(v_src_bucket_ids, '{}') || COALESCE(v_add_bucket_ids, '{}'); + v_all_names := COALESCE(v_src_names, '{}') || COALESCE(v_add_names, '{}'); + + -- Single lock call ensures consistent global ordering across all transactions + IF array_length(v_all_bucket_ids, 1) IS NOT NULL THEN + PERFORM storage.lock_top_prefixes(v_all_bucket_ids, v_all_names); + END IF; + END; + + -- 3) Create destination prefixes (NEW−OLD) BEFORE pruning sources + IF array_length(v_add_bucket_ids, 1) IS NOT NULL THEN + WITH candidates AS ( + SELECT DISTINCT t.bucket_id, unnest(storage.get_prefixes(t.name)) AS name + FROM unnest(v_add_bucket_ids, v_add_names) AS t(bucket_id, name) + WHERE name <> '' + ) + INSERT INTO storage.prefixes (bucket_id, name) + SELECT c.bucket_id, c.name + FROM candidates c + ON CONFLICT DO NOTHING; + END IF; + + -- 4) Prune source prefixes bottom-up for OLD−NEW + IF array_length(v_src_bucket_ids, 1) IS NOT NULL THEN + -- re-entrancy guard so DELETE on prefixes won't recurse + IF current_setting('storage.gc.prefixes', true) <> '1' THEN + PERFORM set_config('storage.gc.prefixes', '1', true); + END IF; + + PERFORM storage.delete_leaf_prefixes(v_src_bucket_ids, v_src_names); + END IF; + + RETURN NULL; +END; +$$; + +-- After DELETE on storage.prefixes +-- - When prefixes are deleted, remove now-empty ancestor prefixes +-- - Guards with `gc.prefixes`, locks, then prunes leaves derived from deleted prefixes +CREATE OR REPLACE FUNCTION storage.prefixes_delete_cleanup() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER +AS $$ +DECLARE + v_bucket_ids text[]; + v_names text[]; +BEGIN + IF current_setting('storage.gc.prefixes', true) = '1' THEN + RETURN NULL; + END IF; + + PERFORM set_config('storage.gc.prefixes', '1', true); + + SELECT COALESCE(array_agg(d.bucket_id), '{}'), + COALESCE(array_agg(d.name), '{}') + INTO v_bucket_ids, v_names + FROM deleted AS d + WHERE d.name <> ''; + + PERFORM storage.lock_top_prefixes(v_bucket_ids, v_names); + PERFORM storage.delete_leaf_prefixes(v_bucket_ids, v_names); + + RETURN NULL; +END; +$$; + +DROP TRIGGER IF EXISTS objects_delete_cleanup ON storage.objects; +DROP TRIGGER IF EXISTS prefixes_delete_cleanup ON storage.prefixes; +DROP TRIGGER IF EXISTS objects_update_cleanup ON storage.objects; + +-- Trigger bindings +CREATE TRIGGER objects_delete_cleanup + AFTER DELETE ON storage.objects + REFERENCING OLD TABLE AS deleted + FOR EACH STATEMENT +EXECUTE FUNCTION storage.objects_delete_cleanup(); + +CREATE TRIGGER prefixes_delete_cleanup + AFTER DELETE ON storage.prefixes + REFERENCING OLD TABLE AS deleted + FOR EACH STATEMENT +EXECUTE FUNCTION storage.prefixes_delete_cleanup(); + +CREATE TRIGGER objects_update_cleanup + AFTER UPDATE ON storage.objects + REFERENCING OLD TABLE AS old_rows NEW TABLE AS new_rows + FOR EACH STATEMENT +EXECUTE FUNCTION storage.objects_update_cleanup(); \ No newline at end of file diff --git a/migrations/tenant/0041-add-object-level-update-trigger.sql b/migrations/tenant/0041-add-object-level-update-trigger.sql new file mode 100644 index 000000000..2ad3134fa --- /dev/null +++ b/migrations/tenant/0041-add-object-level-update-trigger.sql @@ -0,0 +1,82 @@ +-- postgres-migrations ignore +CREATE OR REPLACE FUNCTION "storage"."objects_update_level_trigger"() + RETURNS trigger +AS $func$ +BEGIN + -- Ensure this is an update operation and the name has changed + IF TG_OP = 'UPDATE' AND (NEW."name" <> OLD."name" OR NEW."bucket_id" <> OLD."bucket_id") THEN + -- Set the new level + NEW."level" := "storage"."get_level"(NEW."name"); + END IF; + RETURN NEW; +END; +$func$ LANGUAGE plpgsql VOLATILE; + +CREATE OR REPLACE TRIGGER "objects_update_level_trigger" +BEFORE UPDATE ON "storage"."objects" +FOR EACH ROW +WHEN (NEW.name != OLD.name OR NEW.bucket_id != OLD.bucket_id) +EXECUTE FUNCTION "storage"."objects_update_level_trigger"(); + + +CREATE OR REPLACE FUNCTION storage.delete_leaf_prefixes(bucket_ids text[], names text[]) + RETURNS void + LANGUAGE plpgsql + SECURITY DEFINER +AS $$ +DECLARE + v_rows_deleted integer; +BEGIN + LOOP + WITH candidates AS ( + SELECT DISTINCT + t.bucket_id, + unnest(storage.get_prefixes(t.name)) AS name + FROM unnest(bucket_ids, names) AS t(bucket_id, name) + ), + uniq AS ( + SELECT + bucket_id, + name, + storage.get_level(name) AS level + FROM candidates + WHERE name <> '' + GROUP BY bucket_id, name + ), + leaf AS ( + SELECT + p.bucket_id, + p.name, + p.level + FROM storage.prefixes AS p + JOIN uniq AS u + ON u.bucket_id = p.bucket_id + AND u.name = p.name + AND u.level = p.level + WHERE NOT EXISTS ( + SELECT 1 + FROM storage.objects AS o + WHERE o.bucket_id = p.bucket_id + AND o.level = p.level + 1 + AND o.name COLLATE "C" LIKE p.name || '/%' + ) + AND NOT EXISTS ( + SELECT 1 + FROM storage.prefixes AS c + WHERE c.bucket_id = p.bucket_id + AND c.level = p.level + 1 + AND c.name COLLATE "C" LIKE p.name || '/%' + ) + ) + DELETE + FROM storage.prefixes AS p + USING leaf AS l + WHERE p.bucket_id = l.bucket_id + AND p.name = l.name + AND p.level = l.level; + + GET DIAGNOSTICS v_rows_deleted = ROW_COUNT; + EXIT WHEN v_rows_deleted = 0; + END LOOP; +END; +$$; \ No newline at end of file diff --git a/migrations/tenant/0042-rollback-prefix-triggers.sql b/migrations/tenant/0042-rollback-prefix-triggers.sql new file mode 100644 index 000000000..8370d68f0 --- /dev/null +++ b/migrations/tenant/0042-rollback-prefix-triggers.sql @@ -0,0 +1,33 @@ +-- postgres-migrations ignore +DROP TRIGGER IF EXISTS objects_delete_cleanup ON storage.objects; +DROP TRIGGER IF EXISTS prefixes_delete_cleanup ON storage.prefixes; +DROP TRIGGER IF EXISTS objects_update_cleanup ON storage.objects; +DROP TRIGGER IF EXISTS objects_update_level_trigger ON storage.objects; + +CREATE OR REPLACE TRIGGER "objects_insert_create_prefix" + BEFORE INSERT ON "storage"."objects" + FOR EACH ROW + EXECUTE FUNCTION "storage"."objects_insert_prefix_trigger"(); + +CREATE OR REPLACE TRIGGER "objects_update_create_prefix" + BEFORE UPDATE ON "storage"."objects" + FOR EACH ROW + WHEN (NEW.name != OLD.name) + EXECUTE FUNCTION "storage"."objects_insert_prefix_trigger"(); + +CREATE OR REPLACE TRIGGER "objects_delete_delete_prefix" + AFTER DELETE ON "storage"."objects" + FOR EACH ROW + EXECUTE FUNCTION "storage"."delete_prefix_hierarchy_trigger"(); + +CREATE OR REPLACE TRIGGER "objects_update_create_prefix" + BEFORE UPDATE ON "storage"."objects" + FOR EACH ROW + WHEN (NEW.name != OLD.name OR NEW.bucket_id != OLD.bucket_id) + EXECUTE FUNCTION "storage"."objects_update_prefix_trigger"(); + +-- "storage"."prefixes" +CREATE OR REPLACE TRIGGER "prefixes_delete_hierarchy" + AFTER DELETE ON "storage"."prefixes" + FOR EACH ROW +EXECUTE FUNCTION "storage"."delete_prefix_hierarchy_trigger"(); \ No newline at end of file diff --git a/migrations/tenant/0043-fix-object-level.sql b/migrations/tenant/0043-fix-object-level.sql new file mode 100644 index 000000000..dc0cf69e0 --- /dev/null +++ b/migrations/tenant/0043-fix-object-level.sql @@ -0,0 +1,15 @@ +-- postgres-migrations ignore +DO $$ +BEGIN + IF EXISTS( + SELECT id FROM storage.migrations + WHERE name = 'fix-prefix-race-conditions-optimized' + AND executed_at >= now() - interval '1 minutes' + ) THEN + RETURN; + END IF; + + -- Update all object levels based on their names that are incorrect + UPDATE storage.objects SET level = storage.get_level(name) + WHERE level != storage.get_level(name); +END$$; \ No newline at end of file diff --git a/migrations/tenant/0044-vector-bucket-type.sql b/migrations/tenant/0044-vector-bucket-type.sql new file mode 100644 index 000000000..6ba4ec587 --- /dev/null +++ b/migrations/tenant/0044-vector-bucket-type.sql @@ -0,0 +1,13 @@ +DO $$ + DECLARE + BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_enum + JOIN pg_type ON pg_enum.enumtypid = pg_type.oid + WHERE pg_type.typname = 'buckettype' + AND enumlabel = 'VECTOR' + ) THEN + ALTER TYPE storage.BucketType ADD VALUE 'VECTOR'; + END IF; +END$$; \ No newline at end of file diff --git a/migrations/tenant/0045-vector-buckets.sql b/migrations/tenant/0045-vector-buckets.sql new file mode 100644 index 000000000..23152dfd0 --- /dev/null +++ b/migrations/tenant/0045-vector-buckets.sql @@ -0,0 +1,34 @@ +DO $$ + DECLARE + anon_role text = COALESCE(current_setting('storage.anon_role', true), 'anon'); + authenticated_role text = COALESCE(current_setting('storage.authenticated_role', true), 'authenticated'); + service_role text = COALESCE(current_setting('storage.service_role', true), 'service_role'); + BEGIN + CREATE TABLE IF NOT EXISTS storage.buckets_vectors ( + id text not null primary key, + type storage.BucketType NOT NULL default 'VECTOR', + created_at timestamptz NOT NULL default now(), + updated_at timestamptz NOT NULL default now() + ); + + CREATE TABLE IF NOT EXISTS storage.vector_indexes + ( + id text primary key default gen_random_uuid(), + name text COLLATE "C" NOT NULL, + bucket_id text NOT NULL references storage.buckets_vectors (id), + data_type text NOT NULL, + dimension integer NOT NULL, + distance_metric text NOT NULL, + metadata_configuration jsonb NULL, + created_at timestamptz NOT NULL default now(), + updated_at timestamptz NOT NULL default now() + ); + + ALTER TABLE storage.buckets_vectors ENABLE ROW LEVEL SECURITY; + ALTER TABLE storage.vector_indexes ENABLE ROW LEVEL SECURITY; + + EXECUTE 'GRANT SELECT ON TABLE storage.buckets_vectors TO ' || service_role || ', ' || authenticated_role || ', ' || anon_role; + EXECUTE 'GRANT SELECT ON TABLE storage.vector_indexes TO ' || service_role || ', ' || authenticated_role || ', ' || anon_role; + + CREATE UNIQUE INDEX IF NOT EXISTS vector_indexes_name_bucket_id_idx ON storage.vector_indexes (name, bucket_id); +END$$; \ No newline at end of file diff --git a/migrations/tenant/0046-buckets-objects-grants.sql b/migrations/tenant/0046-buckets-objects-grants.sql new file mode 100644 index 000000000..45c3a621f --- /dev/null +++ b/migrations/tenant/0046-buckets-objects-grants.sql @@ -0,0 +1,15 @@ +do $$ +declare + super_user text = coalesce(current_setting('storage.super_user', true), 'postgres'); +begin + execute 'grant all on storage.buckets, storage.objects to ' || super_user || ' with grant option'; +end $$; + +do $$ +declare + anon_role text = coalesce(current_setting('storage.anon_role', true), 'anon'); + authenticated_role text = coalesce(current_setting('storage.authenticated_role', true), 'authenticated'); + service_role text = coalesce(current_setting('storage.service_role', true), 'service_role'); +begin + execute 'grant all on storage.buckets, storage.objects to ' || service_role || ', ' || authenticated_role || ', ' || anon_role; +end $$; diff --git a/migrations/tenant/0047-iceberg-table-metadata.sql b/migrations/tenant/0047-iceberg-table-metadata.sql new file mode 100644 index 000000000..e37d87742 --- /dev/null +++ b/migrations/tenant/0047-iceberg-table-metadata.sql @@ -0,0 +1,16 @@ +DO $$ + DECLARE + is_multitenant bool = COALESCE(current_setting('storage.multitenant', true), 'false')::boolean; + BEGIN + + IF is_multitenant THEN + RETURN; + END IF; + + ALTER TABLE iceberg_namespaces ADD COLUMN IF NOT EXISTS metadata JSONB NOT NULL DEFAULT '{}'; + ALTER TABLE iceberg_tables ADD COLUMN IF NOT EXISTS remote_table_id TEXT NULL; + + ALTER TABLE iceberg_tables ADD COLUMN IF NOT EXISTS shard_key TEXT NULL; + ALTER TABLE iceberg_tables ADD COLUMN IF NOT EXISTS shard_id TEXT NULL; +END +$$; \ No newline at end of file diff --git a/migrations/tenant/0048-iceberg-catalog-ids.sql b/migrations/tenant/0048-iceberg-catalog-ids.sql new file mode 100644 index 000000000..188b4a961 --- /dev/null +++ b/migrations/tenant/0048-iceberg-catalog-ids.sql @@ -0,0 +1,98 @@ +DO $$ + DECLARE + is_multitenant bool = COALESCE(current_setting('storage.multitenant', true), 'false')::boolean; + drop_constraint_sql text; + BEGIN + + IF is_multitenant = false THEN + ALTER TABLE storage.iceberg_namespaces DROP CONSTRAINT IF EXISTS iceberg_namespaces_catalog_id_fkey; + ALTER TABLE storage.iceberg_tables DROP CONSTRAINT IF EXISTS iceberg_tables_catalog_id_fkey; + ALTER TABLE storage.iceberg_namespaces DROP CONSTRAINT IF EXISTS iceberg_namespaces_bucket_id_fkey; + ALTER TABLE storage.iceberg_tables DROP CONSTRAINT IF EXISTS iceberg_tables_bucket_id_fkey; + END IF; + + -- remove primary key on iceberg_catalogs id + SELECT concat('ALTER TABLE storage.buckets_analytics DROP CONSTRAINT ', constraint_name) + INTO drop_constraint_sql + FROM information_schema.table_constraints + WHERE table_schema = 'storage' + AND table_name = 'buckets_analytics' + AND constraint_type = 'PRIMARY KEY'; + + IF drop_constraint_sql IS NOT NULL THEN + EXECUTE drop_constraint_sql; + END IF; + + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'buckets_analytics' AND column_name = 'name') THEN + ALTER TABLE storage.buckets_analytics RENAME COLUMN id TO name; + END IF; + + ALTER TABLE storage.buckets_analytics ADD COLUMN IF NOT EXISTS id uuid NOT NULL DEFAULT gen_random_uuid(); + ALTER TABLE storage.buckets_analytics ADD PRIMARY KEY (id); + ALTER TABLE storage.buckets_analytics ADD COLUMN IF NOT EXISTS deleted_at timestamptz NULL; + + CREATE UNIQUE INDEX IF NOT EXISTS buckets_analytics_unique_name_idx + ON storage.buckets_analytics (name) WHERE deleted_at IS NULL; + + IF is_multitenant THEN + RETURN; + END IF; + + DROP INDEX IF EXISTS idx_iceberg_namespaces_bucket_id; + DROP INDEX IF EXISTS idx_iceberg_tables_namespace_id; + + -- remove constraint on iceberg_namespaces bucket_id + ALTER TABLE storage.iceberg_namespaces DROP CONSTRAINT IF EXISTS iceberg_namespaces_bucket_id_fkey; + -- remove constraint on iceberg_tables bucket_id + ALTER TABLE storage.iceberg_tables DROP CONSTRAINT IF EXISTS iceberg_tables_bucket_id_fkey; + + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'iceberg_namespaces' AND column_name = 'bucket_name') THEN + ALTER TABLE storage.iceberg_namespaces RENAME COLUMN bucket_id to bucket_name; + END IF; + + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'iceberg_tables' AND column_name = 'bucket_name') THEN + ALTER TABLE storage.iceberg_tables RENAME COLUMN bucket_id to bucket_name; + END IF; + + ALTER TABLE storage.iceberg_namespaces ADD COLUMN IF NOT EXISTS catalog_id uuid NULL; + ALTER TABLE storage.iceberg_tables ADD COLUMN IF NOT EXISTS catalog_id uuid NULL; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'storage' + AND table_name = 'iceberg_namespaces' + AND constraint_name = 'iceberg_namespaces_catalog_id_fkey' + ) THEN + ALTER TABLE storage.iceberg_namespaces ADD CONSTRAINT iceberg_namespaces_catalog_id_fkey + FOREIGN KEY (catalog_id) REFERENCES storage.buckets_analytics(id) ON DELETE CASCADE; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'storage' + AND table_name = 'iceberg_tables' + AND constraint_name = 'iceberg_tables_catalog_id_fkey' + ) THEN + ALTER TABLE storage.iceberg_tables ADD CONSTRAINT iceberg_tables_catalog_id_fkey + FOREIGN KEY (catalog_id) REFERENCES storage.buckets_analytics(id) ON DELETE CASCADE; + END IF; + + CREATE UNIQUE INDEX IF NOT EXISTS idx_iceberg_namespaces_bucket_id ON storage.iceberg_namespaces (catalog_id, name); + CREATE UNIQUE INDEX IF NOT EXISTS idx_iceberg_tables_namespace_id ON storage.iceberg_tables (catalog_id, namespace_id, name); + CREATE UNIQUE INDEX IF NOT EXISTS idx_iceberg_tables_location ON storage.iceberg_tables (location); + + -- Backfill catalog_id for existing namespaces and tables + UPDATE storage.iceberg_tables it + SET catalog_id = c.id + FROM storage.buckets_analytics c + WHERE c.name = it.bucket_name; + + UPDATE storage.iceberg_namespaces iname + SET catalog_id = c.id + FROM storage.buckets_analytics c + WHERE c.name = iname.bucket_name; + + ALTER TABLE storage.iceberg_namespaces ALTER COLUMN catalog_id SET NOT NULL; + ALTER TABLE storage.iceberg_tables ALTER COLUMN catalog_id SET NOT NULL; +END +$$; \ No newline at end of file diff --git a/migrations/tenant/0049-buckets-objects-grants-postgres.sql b/migrations/tenant/0049-buckets-objects-grants-postgres.sql new file mode 100644 index 000000000..e81a70da6 --- /dev/null +++ b/migrations/tenant/0049-buckets-objects-grants-postgres.sql @@ -0,0 +1,6 @@ +do $$ +begin + if exists (select from pg_roles where rolname = 'postgres') then + grant all on storage.buckets, storage.objects to postgres with grant option; + end if; +end $$; diff --git a/migrations/tenant/0050-search-v2-optimised.sql b/migrations/tenant/0050-search-v2-optimised.sql new file mode 100644 index 000000000..4d616a3f5 --- /dev/null +++ b/migrations/tenant/0050-search-v2-optimised.sql @@ -0,0 +1,807 @@ +-- ============================================================================ +-- search_v3: Efficient S3-like key listing WITHOUT prefixes table +-- ============================================================================ +-- +-- Key improvements over search_v2: +-- 1. No dependency on prefixes table or triggers +-- 2. Derives common prefixes on-the-fly from objects table +-- 3. Uses skip-scan algorithm for O(k * log n) performance on name sorting +-- 4. Correct pagination across mixed prefixes and objects +-- 5. Supports sorting by name, created_at, updated_at +-- +-- Required index (already exists from migration 0020): +-- idx_objects_bucket_id_name ON storage.objects (bucket_id, (name COLLATE "C")) +-- +-- Function signature matches search_v2 for drop-in replacement +-- ============================================================================ + +-- ============================================================================ +-- HELPER FUNCTION: Extract common prefix from a key +-- ============================================================================ +-- Given key "data/tenant-001/file.txt" with prefix "data/" and delimiter "/" +-- Returns "data/tenant-001/" (the first delimiter-terminated segment after prefix) +-- Returns NULL if key is a leaf object (no delimiter after prefix) +-- +-- Example mappings (prefix="data/", delimiter="/"): +-- "data/tenant/file.txt" -> "data/tenant/" +-- "data/tenant/" -> "data/tenant/" +-- "data/file.txt" -> NULL (leaf object) +-- "data/a/b/c/d.txt" -> "data/a/" +-- ============================================================================ +CREATE OR REPLACE FUNCTION storage.get_common_prefix( + p_key TEXT, + p_prefix TEXT, + p_delimiter TEXT +) RETURNS TEXT IMMUTABLE LANGUAGE sql AS $$ +SELECT CASE + WHEN position(p_delimiter IN substring(p_key FROM length(p_prefix) + 1)) > 0 + THEN left(p_key, length(p_prefix) + position(p_delimiter IN substring(p_key FROM length(p_prefix) + 1))) + ELSE NULL +END; +$$; + +-- ============================================================================ +-- list_objects_with_delimiter: S3-compatible listing with hybrid skip-scan +-- ============================================================================ +-- This is the core function for efficient object listing with delimiter support. +-- Uses a HYBRID approach for optimal performance: +-- 1. LIMIT 1 for folder discovery (index-only scan, very fast) +-- 2. Batch fetching for files (heap access needed anyway) +-- +-- This avoids the performance trap of batch-fetching 2000 rows with all columns +-- just to discover a folder name (which only needs the name column). +-- +-- Performance comparison on 10M rows with 1000 folders: +-- - Batch LIMIT 2000: ~1900ms (reads 98,000 heap pages) +-- - Hybrid LIMIT 1: ~28ms (reads 1,000 index pages for folders) +-- +-- Parameters: +-- bucket_id: The bucket identifier +-- prefix_param: Filter objects starting with this prefix +-- delimiter_param: The delimiter for grouping common prefixes (e.g., '/') +-- max_keys: Maximum number of results to return +-- start_after: Start listing after this key (for pagination) +-- next_token: Continuation token (same as start_after for compatibility) +-- sort_order: 'asc' or 'desc' (default 'asc') +-- ============================================================================ + +-- Drop old function signatures with exception handling +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.search(prefix text, bucketname text, limits integer, levels integer, offsets integer); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.search_v2(prefix text, bucket_name text, limits integer, levels integer, start_after text); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.list_objects_with_delimiter(bucket_id text, prefix_param text, delimiter_param text, max_keys integer, start_after text, next_token text); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +CREATE OR REPLACE FUNCTION storage.list_objects_with_delimiter( + _bucket_id text, + prefix_param text, + delimiter_param text, + max_keys integer DEFAULT 100, + start_after text DEFAULT '', + next_token text DEFAULT '', + sort_order text DEFAULT 'asc' +) +RETURNS TABLE ( + name text, + id uuid, + metadata jsonb, + updated_at timestamptz, + created_at timestamptz, + last_accessed_at timestamptz +) +SECURITY INVOKER +LANGUAGE plpgsql STABLE +AS $func$ +DECLARE + v_peek_name TEXT; + v_current RECORD; + v_common_prefix TEXT; + + -- Configuration + v_is_asc BOOLEAN; + v_prefix TEXT; + v_start TEXT; + v_upper_bound TEXT; + v_file_batch_size INT; + + -- Seek state + v_next_seek TEXT; + v_count INT := 0; + + -- Dynamic SQL for batch query only + v_batch_query TEXT; + +BEGIN + -- ======================================================================== + -- INITIALIZATION + -- ======================================================================== + v_is_asc := lower(coalesce(sort_order, 'asc')) = 'asc'; + v_prefix := coalesce(prefix_param, ''); + v_start := CASE WHEN coalesce(next_token, '') <> '' THEN next_token ELSE coalesce(start_after, '') END; + v_file_batch_size := LEAST(GREATEST(max_keys * 2, 100), 1000); + + -- Calculate upper bound for prefix filtering (bytewise, using COLLATE "C") + IF v_prefix = '' THEN + v_upper_bound := NULL; + ELSIF right(v_prefix, 1) = delimiter_param THEN + v_upper_bound := left(v_prefix, -1) || chr(ascii(delimiter_param) + 1); + ELSE + v_upper_bound := left(v_prefix, -1) || chr(ascii(right(v_prefix, 1)) + 1); + END IF; + + -- Build batch query (dynamic SQL - called infrequently, amortized over many rows) + IF v_is_asc THEN + IF v_upper_bound IS NOT NULL THEN + v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' || + 'FROM storage.objects o WHERE o.bucket_id = $1 AND o.name COLLATE "C" >= $2 ' || + 'AND o.name COLLATE "C" < $3 ORDER BY o.name COLLATE "C" ASC LIMIT $4'; + ELSE + v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' || + 'FROM storage.objects o WHERE o.bucket_id = $1 AND o.name COLLATE "C" >= $2 ' || + 'ORDER BY o.name COLLATE "C" ASC LIMIT $4'; + END IF; + ELSE + IF v_upper_bound IS NOT NULL THEN + v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' || + 'FROM storage.objects o WHERE o.bucket_id = $1 AND o.name COLLATE "C" < $2 ' || + 'AND o.name COLLATE "C" >= $3 ORDER BY o.name COLLATE "C" DESC LIMIT $4'; + ELSE + v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' || + 'FROM storage.objects o WHERE o.bucket_id = $1 AND o.name COLLATE "C" < $2 ' || + 'ORDER BY o.name COLLATE "C" DESC LIMIT $4'; + END IF; + END IF; + + -- ======================================================================== + -- SEEK INITIALIZATION: Determine starting position + -- ======================================================================== + IF v_start = '' THEN + IF v_is_asc THEN + v_next_seek := v_prefix; + ELSE + -- DESC without cursor: find the last item in range + IF v_upper_bound IS NOT NULL THEN + SELECT o.name INTO v_next_seek FROM storage.objects o + WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" >= v_prefix AND o.name COLLATE "C" < v_upper_bound + ORDER BY o.name COLLATE "C" DESC LIMIT 1; + ELSIF v_prefix <> '' THEN + SELECT o.name INTO v_next_seek FROM storage.objects o + WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" >= v_prefix + ORDER BY o.name COLLATE "C" DESC LIMIT 1; + ELSE + SELECT o.name INTO v_next_seek FROM storage.objects o + WHERE o.bucket_id = _bucket_id + ORDER BY o.name COLLATE "C" DESC LIMIT 1; + END IF; + + IF v_next_seek IS NOT NULL THEN + v_next_seek := v_next_seek || delimiter_param; + ELSE + RETURN; + END IF; + END IF; + ELSE + -- Cursor provided: determine if it refers to a folder or leaf + IF EXISTS ( + SELECT 1 FROM storage.objects o + WHERE o.bucket_id = _bucket_id + AND o.name COLLATE "C" LIKE v_start || delimiter_param || '%' + LIMIT 1 + ) THEN + -- Cursor refers to a folder + IF v_is_asc THEN + v_next_seek := v_start || chr(ascii(delimiter_param) + 1); + ELSE + v_next_seek := v_start || delimiter_param; + END IF; + ELSE + -- Cursor refers to a leaf object + IF v_is_asc THEN + v_next_seek := v_start || delimiter_param; + ELSE + v_next_seek := v_start; + END IF; + END IF; + END IF; + + -- ======================================================================== + -- MAIN LOOP: Hybrid peek-then-batch algorithm + -- Uses STATIC SQL for peek (hot path) and DYNAMIC SQL for batch + -- ======================================================================== + LOOP + EXIT WHEN v_count >= max_keys; + + -- STEP 1: PEEK using STATIC SQL (plan cached, very fast) + IF v_is_asc THEN + IF v_upper_bound IS NOT NULL THEN + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" >= v_next_seek AND o.name COLLATE "C" < v_upper_bound + ORDER BY o.name COLLATE "C" ASC LIMIT 1; + ELSE + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" >= v_next_seek + ORDER BY o.name COLLATE "C" ASC LIMIT 1; + END IF; + ELSE + IF v_upper_bound IS NOT NULL THEN + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" < v_next_seek AND o.name COLLATE "C" >= v_prefix + ORDER BY o.name COLLATE "C" DESC LIMIT 1; + ELSIF v_prefix <> '' THEN + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" < v_next_seek AND o.name COLLATE "C" >= v_prefix + ORDER BY o.name COLLATE "C" DESC LIMIT 1; + ELSE + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = _bucket_id AND o.name COLLATE "C" < v_next_seek + ORDER BY o.name COLLATE "C" DESC LIMIT 1; + END IF; + END IF; + + EXIT WHEN v_peek_name IS NULL; + + -- STEP 2: Check if this is a FOLDER or FILE + v_common_prefix := storage.get_common_prefix(v_peek_name, v_prefix, delimiter_param); + + IF v_common_prefix IS NOT NULL THEN + -- FOLDER: Emit and skip to next folder (no heap access needed) + name := rtrim(v_common_prefix, delimiter_param); + id := NULL; + updated_at := NULL; + created_at := NULL; + last_accessed_at := NULL; + metadata := NULL; + RETURN NEXT; + v_count := v_count + 1; + + -- Advance seek past the folder range + IF v_is_asc THEN + v_next_seek := left(v_common_prefix, -1) || chr(ascii(delimiter_param) + 1); + ELSE + v_next_seek := v_common_prefix; + END IF; + ELSE + -- FILE: Batch fetch using DYNAMIC SQL (overhead amortized over many rows) + -- For ASC: upper_bound is the exclusive upper limit (< condition) + -- For DESC: prefix is the inclusive lower limit (>= condition) + FOR v_current IN EXECUTE v_batch_query USING _bucket_id, v_next_seek, + CASE WHEN v_is_asc THEN COALESCE(v_upper_bound, v_prefix) ELSE v_prefix END, v_file_batch_size + LOOP + v_common_prefix := storage.get_common_prefix(v_current.name, v_prefix, delimiter_param); + + IF v_common_prefix IS NOT NULL THEN + -- Hit a folder: exit batch, let peek handle it + v_next_seek := v_current.name; + EXIT; + END IF; + + -- Emit file + name := v_current.name; + id := v_current.id; + updated_at := v_current.updated_at; + created_at := v_current.created_at; + last_accessed_at := v_current.last_accessed_at; + metadata := v_current.metadata; + RETURN NEXT; + v_count := v_count + 1; + + -- Advance seek past this file + IF v_is_asc THEN + v_next_seek := v_current.name || delimiter_param; + ELSE + v_next_seek := v_current.name; + END IF; + + EXIT WHEN v_count >= max_keys; + END LOOP; + END IF; + END LOOP; +END; +$func$; + + +-- ============================================================================ +-- search: Legacy function with offset-based pagination using hybrid skip-scan +-- ============================================================================ +-- Maintains backwards compatibility with the original search function signature. +-- Uses HYBRID approach for optimal performance: +-- 1. STATIC SQL peek for folder discovery (plan cached, very fast) +-- 2. DYNAMIC SQL batch for files (overhead amortized over many rows) +-- Falls back to path_tokens approach for non-name sorting. +-- ============================================================================ +CREATE OR REPLACE FUNCTION storage.search( + prefix text, + bucketname text, + limits int DEFAULT 100, + levels int DEFAULT 1, + offsets int DEFAULT 0, + search text DEFAULT '', + sortcolumn text DEFAULT 'name', + sortorder text DEFAULT 'asc' +) +RETURNS TABLE ( + name text, + id uuid, + updated_at timestamptz, + created_at timestamptz, + last_accessed_at timestamptz, + metadata jsonb +) +SECURITY INVOKER +LANGUAGE plpgsql STABLE +AS $func$ +DECLARE + v_peek_name TEXT; + v_current RECORD; + v_common_prefix TEXT; + v_delimiter CONSTANT TEXT := '/'; + + -- Configuration + v_limit INT; + v_prefix TEXT; + v_prefix_lower TEXT; + v_is_asc BOOLEAN; + v_order_by TEXT; + v_sort_order TEXT; + v_upper_bound TEXT; + v_file_batch_size INT; + + -- Dynamic SQL for batch query only + v_batch_query TEXT; + + -- Seek state + v_next_seek TEXT; + v_count INT := 0; + v_skipped INT := 0; +BEGIN + -- ======================================================================== + -- INITIALIZATION + -- ======================================================================== + v_limit := LEAST(coalesce(limits, 100), 1500); + v_prefix := coalesce(prefix, '') || coalesce(search, ''); + v_prefix_lower := lower(v_prefix); + v_is_asc := lower(coalesce(sortorder, 'asc')) = 'asc'; + v_file_batch_size := LEAST(GREATEST(v_limit * 2, 100), 1000); + + -- Validate sort column + CASE lower(coalesce(sortcolumn, 'name')) + WHEN 'name' THEN v_order_by := 'name'; + WHEN 'updated_at' THEN v_order_by := 'updated_at'; + WHEN 'created_at' THEN v_order_by := 'created_at'; + WHEN 'last_accessed_at' THEN v_order_by := 'last_accessed_at'; + ELSE v_order_by := 'name'; + END CASE; + + v_sort_order := CASE WHEN v_is_asc THEN 'asc' ELSE 'desc' END; + + -- ======================================================================== + -- NON-NAME SORTING: Use path_tokens approach (unchanged) + -- ======================================================================== + IF v_order_by != 'name' THEN + RETURN QUERY EXECUTE format( + $sql$ + WITH folders AS ( + SELECT path_tokens[$1] AS folder + FROM storage.objects + WHERE objects.name ILIKE $2 || '%%' + AND bucket_id = $3 + AND array_length(objects.path_tokens, 1) <> $1 + GROUP BY folder + ORDER BY folder %s + ) + (SELECT folder AS "name", + NULL::uuid AS id, + NULL::timestamptz AS updated_at, + NULL::timestamptz AS created_at, + NULL::timestamptz AS last_accessed_at, + NULL::jsonb AS metadata FROM folders) + UNION ALL + (SELECT path_tokens[$1] AS "name", + id, updated_at, created_at, last_accessed_at, metadata + FROM storage.objects + WHERE objects.name ILIKE $2 || '%%' + AND bucket_id = $3 + AND array_length(objects.path_tokens, 1) = $1 + ORDER BY %I %s) + LIMIT $4 OFFSET $5 + $sql$, v_sort_order, v_order_by, v_sort_order + ) USING levels, v_prefix, bucketname, v_limit, offsets; + RETURN; + END IF; + + -- ======================================================================== + -- NAME SORTING: Hybrid skip-scan with batch optimization + -- ======================================================================== + + -- Calculate upper bound for prefix filtering + IF v_prefix_lower = '' THEN + v_upper_bound := NULL; + ELSIF right(v_prefix_lower, 1) = v_delimiter THEN + v_upper_bound := left(v_prefix_lower, -1) || chr(ascii(v_delimiter) + 1); + ELSE + v_upper_bound := left(v_prefix_lower, -1) || chr(ascii(right(v_prefix_lower, 1)) + 1); + END IF; + + -- Build batch query (dynamic SQL - called infrequently, amortized over many rows) + IF v_is_asc THEN + IF v_upper_bound IS NOT NULL THEN + v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' || + 'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" >= $2 ' || + 'AND lower(o.name) COLLATE "C" < $3 ORDER BY lower(o.name) COLLATE "C" ASC LIMIT $4'; + ELSE + v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' || + 'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" >= $2 ' || + 'ORDER BY lower(o.name) COLLATE "C" ASC LIMIT $4'; + END IF; + ELSE + IF v_upper_bound IS NOT NULL THEN + v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' || + 'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" < $2 ' || + 'AND lower(o.name) COLLATE "C" >= $3 ORDER BY lower(o.name) COLLATE "C" DESC LIMIT $4'; + ELSE + v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' || + 'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" < $2 ' || + 'ORDER BY lower(o.name) COLLATE "C" DESC LIMIT $4'; + END IF; + END IF; + + -- Initialize seek position + IF v_is_asc THEN + v_next_seek := v_prefix_lower; + ELSE + -- DESC: find the last item in range first (static SQL) + IF v_upper_bound IS NOT NULL THEN + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_prefix_lower AND lower(o.name) COLLATE "C" < v_upper_bound + ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1; + ELSIF v_prefix_lower <> '' THEN + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_prefix_lower + ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1; + ELSE + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname + ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1; + END IF; + + IF v_peek_name IS NOT NULL THEN + v_next_seek := lower(v_peek_name) || v_delimiter; + ELSE + RETURN; + END IF; + END IF; + + -- ======================================================================== + -- MAIN LOOP: Hybrid peek-then-batch algorithm + -- Uses STATIC SQL for peek (hot path) and DYNAMIC SQL for batch + -- ======================================================================== + LOOP + EXIT WHEN v_count >= v_limit; + + -- STEP 1: PEEK using STATIC SQL (plan cached, very fast) + IF v_is_asc THEN + IF v_upper_bound IS NOT NULL THEN + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_next_seek AND lower(o.name) COLLATE "C" < v_upper_bound + ORDER BY lower(o.name) COLLATE "C" ASC LIMIT 1; + ELSE + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_next_seek + ORDER BY lower(o.name) COLLATE "C" ASC LIMIT 1; + END IF; + ELSE + IF v_upper_bound IS NOT NULL THEN + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" < v_next_seek AND lower(o.name) COLLATE "C" >= v_prefix_lower + ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1; + ELSIF v_prefix_lower <> '' THEN + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" < v_next_seek AND lower(o.name) COLLATE "C" >= v_prefix_lower + ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1; + ELSE + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" < v_next_seek + ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1; + END IF; + END IF; + + EXIT WHEN v_peek_name IS NULL; + + -- STEP 2: Check if this is a FOLDER or FILE + v_common_prefix := storage.get_common_prefix(lower(v_peek_name), v_prefix_lower, v_delimiter); + + IF v_common_prefix IS NOT NULL THEN + -- FOLDER: Handle offset, emit if needed, skip to next folder + IF v_skipped < offsets THEN + v_skipped := v_skipped + 1; + ELSE + name := split_part(rtrim(v_common_prefix, v_delimiter), v_delimiter, levels); + id := NULL; + updated_at := NULL; + created_at := NULL; + last_accessed_at := NULL; + metadata := NULL; + RETURN NEXT; + v_count := v_count + 1; + END IF; + + -- Advance seek past the folder range + IF v_is_asc THEN + v_next_seek := lower(left(v_common_prefix, -1)) || chr(ascii(v_delimiter) + 1); + ELSE + v_next_seek := lower(v_common_prefix); + END IF; + ELSE + -- FILE: Batch fetch using DYNAMIC SQL (overhead amortized over many rows) + -- For ASC: upper_bound is the exclusive upper limit (< condition) + -- For DESC: prefix_lower is the inclusive lower limit (>= condition) + FOR v_current IN EXECUTE v_batch_query + USING bucketname, v_next_seek, + CASE WHEN v_is_asc THEN COALESCE(v_upper_bound, v_prefix_lower) ELSE v_prefix_lower END, v_file_batch_size + LOOP + v_common_prefix := storage.get_common_prefix(lower(v_current.name), v_prefix_lower, v_delimiter); + + IF v_common_prefix IS NOT NULL THEN + -- Hit a folder: exit batch, let peek handle it + v_next_seek := lower(v_current.name); + EXIT; + END IF; + + -- Handle offset skipping + IF v_skipped < offsets THEN + v_skipped := v_skipped + 1; + ELSE + -- Emit file + name := split_part(v_current.name, v_delimiter, levels); + id := v_current.id; + updated_at := v_current.updated_at; + created_at := v_current.created_at; + last_accessed_at := v_current.last_accessed_at; + metadata := v_current.metadata; + RETURN NEXT; + v_count := v_count + 1; + END IF; + + -- Advance seek past this file + IF v_is_asc THEN + v_next_seek := lower(v_current.name) || v_delimiter; + ELSE + v_next_seek := lower(v_current.name); + END IF; + + EXIT WHEN v_count >= v_limit; + END LOOP; + END IF; + END LOOP; +END; +$func$; + + +-- ============================================================================ +-- search_v2: Main entry point (same signature as search_v2) +-- ============================================================================ +CREATE OR REPLACE FUNCTION storage.search_v2( + prefix text, + bucket_name text, + limits int DEFAULT 100, + levels int DEFAULT 1, + start_after text DEFAULT '', + sort_order text DEFAULT 'asc', + sort_column text DEFAULT 'name', + sort_column_after text DEFAULT '' +) +RETURNS TABLE ( + key text, + name text, + id uuid, + updated_at timestamptz, + created_at timestamptz, + last_accessed_at timestamptz, + metadata jsonb +) +SECURITY INVOKER +LANGUAGE plpgsql STABLE +AS $func$ +DECLARE + v_sort_col text; + v_sort_ord text; + v_limit int; +BEGIN + -- Cap limit to maximum of 1500 records + v_limit := LEAST(coalesce(limits, 100), 1500); + + -- Validate and normalize sort_order + v_sort_ord := lower(coalesce(sort_order, 'asc')); + IF v_sort_ord NOT IN ('asc', 'desc') THEN + v_sort_ord := 'asc'; + END IF; + + -- Validate and normalize sort_column + v_sort_col := lower(coalesce(sort_column, 'name')); + IF v_sort_col NOT IN ('name', 'updated_at', 'created_at') THEN + v_sort_col := 'name'; + END IF; + + -- Route to appropriate implementation + IF v_sort_col = 'name' THEN + -- Use list_objects_with_delimiter for name sorting (most efficient: O(k * log n)) + RETURN QUERY + SELECT + split_part(l.name, '/', levels) AS key, + l.name AS name, + l.id, + l.updated_at, + l.created_at, + l.last_accessed_at, + l.metadata + FROM storage.list_objects_with_delimiter( + bucket_name, + coalesce(prefix, ''), + '/', + v_limit, + start_after, + '', + v_sort_ord + ) l; + ELSE + -- Use aggregation approach for timestamp sorting + -- Not efficient for large datasets but supports correct pagination + RETURN QUERY SELECT * FROM storage.search_by_timestamp( + prefix, bucket_name, v_limit, levels, start_after, + v_sort_ord, v_sort_col, sort_column_after + ); + END IF; +END; +$func$; + + +-- ============================================================================ +-- search_by_timestamp: Aggregation-based implementation for timestamp sorting +-- ============================================================================ +CREATE OR REPLACE FUNCTION storage.search_by_timestamp( + p_prefix text, + p_bucket_id text, + p_limit int, + p_level int, + p_start_after text, + p_sort_order text, + p_sort_column text, + p_sort_column_after text +) +RETURNS TABLE ( + key text, + name text, + id uuid, + updated_at timestamptz, + created_at timestamptz, + last_accessed_at timestamptz, + metadata jsonb +) +SECURITY INVOKER +LANGUAGE plpgsql STABLE +AS $func$ +DECLARE + v_cursor_op text; + v_query text; + v_prefix text; +BEGIN + v_prefix := coalesce(p_prefix, ''); + + IF p_sort_order = 'asc' THEN + v_cursor_op := '>'; + ELSE + v_cursor_op := '<'; + END IF; + + v_query := format($sql$ + WITH raw_objects AS ( + SELECT + o.name AS obj_name, + o.id AS obj_id, + o.updated_at AS obj_updated_at, + o.created_at AS obj_created_at, + o.last_accessed_at AS obj_last_accessed_at, + o.metadata AS obj_metadata, + storage.get_common_prefix(o.name, $1, '/') AS common_prefix + FROM storage.objects o + WHERE o.bucket_id = $2 + AND o.name COLLATE "C" LIKE $1 || '%%' + ), + -- Aggregate common prefixes (folders) + -- Both created_at and updated_at use MIN(obj_created_at) to match the old prefixes table behavior + aggregated_prefixes AS ( + SELECT + rtrim(common_prefix, '/') AS name, + NULL::uuid AS id, + MIN(obj_created_at) AS updated_at, + MIN(obj_created_at) AS created_at, + NULL::timestamptz AS last_accessed_at, + NULL::jsonb AS metadata, + TRUE AS is_prefix + FROM raw_objects + WHERE common_prefix IS NOT NULL + GROUP BY common_prefix + ), + leaf_objects AS ( + SELECT + obj_name AS name, + obj_id AS id, + obj_updated_at AS updated_at, + obj_created_at AS created_at, + obj_last_accessed_at AS last_accessed_at, + obj_metadata AS metadata, + FALSE AS is_prefix + FROM raw_objects + WHERE common_prefix IS NULL + ), + combined AS ( + SELECT * FROM aggregated_prefixes + UNION ALL + SELECT * FROM leaf_objects + ), + filtered AS ( + SELECT * + FROM combined + WHERE ( + $5 = '' + OR ROW( + date_trunc('milliseconds', %I), + name COLLATE "C" + ) %s ROW( + COALESCE(NULLIF($6, '')::timestamptz, 'epoch'::timestamptz), + $5 + ) + ) + ) + SELECT + split_part(name, '/', $3) AS key, + name, + id, + updated_at, + created_at, + last_accessed_at, + metadata + FROM filtered + ORDER BY + COALESCE(date_trunc('milliseconds', %I), 'epoch'::timestamptz) %s, + name COLLATE "C" %s + LIMIT $4 + $sql$, + p_sort_column, + v_cursor_op, + p_sort_column, + p_sort_order, + p_sort_order + ); + + RETURN QUERY EXECUTE v_query + USING v_prefix, p_bucket_id, p_level, p_limit, p_start_after, p_sort_column_after; +END; +$func$; + + +-- ============================================================================ +-- MIGRATION COMMANDS +-- ============================================================================ + +-- Drop triggers that maintain the prefixes table +DROP TRIGGER IF EXISTS objects_insert_create_prefix ON storage.objects; +DROP TRIGGER IF EXISTS objects_update_create_prefix ON storage.objects; +DROP TRIGGER IF EXISTS objects_delete_cleanup ON storage.objects; +DROP TRIGGER IF EXISTS objects_delete_delete_prefix ON storage.objects; +DROP TRIGGER IF EXISTS objects_update_cleanup ON storage.objects; \ No newline at end of file diff --git a/migrations/tenant/0051-index-backward-compatible-search.sql b/migrations/tenant/0051-index-backward-compatible-search.sql new file mode 100644 index 000000000..11ea6093b --- /dev/null +++ b/migrations/tenant/0051-index-backward-compatible-search.sql @@ -0,0 +1,5 @@ +-- postgres-migrations disable-transaction +-- Create index for case-insensitive name search in storage.search function +-- Must use COLLATE "C" to match the query comparisons for index usage +CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_objects_bucket_id_name_lower + ON storage.objects (bucket_id, (lower(name) COLLATE "C")); \ No newline at end of file diff --git a/migrations/tenant/0052-drop-not-used-indexes-and-functions.sql b/migrations/tenant/0052-drop-not-used-indexes-and-functions.sql new file mode 100644 index 000000000..660931f1d --- /dev/null +++ b/migrations/tenant/0052-drop-not-used-indexes-and-functions.sql @@ -0,0 +1,134 @@ +-- Drop triggers if table exists +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema='storage' AND table_name='prefixes') THEN + DROP TRIGGER IF EXISTS prefixes_create_hierarchy ON storage.prefixes; + DROP TRIGGER IF EXISTS prefixes_delete_hierarchy ON storage.prefixes; + END IF; +EXCEPTION WHEN OTHERS THEN + NULL; +END; +$$; + +DO $$ + BEGIN + ALTER TABLE storage.objects DROP COLUMN IF EXISTS level; + EXCEPTION WHEN OTHERS THEN + NULL; + END; +$$; + +DROP TABLE IF EXISTS storage.prefixes cascade; + +-- Drop functions with exception handling so failures don't block migration +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.objects_delete_cleanup(); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.objects_update_cleanup(); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.prefixes_delete_cleanup(); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.objects_insert_prefix_trigger(); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.delete_prefix_hierarchy_trigger(); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.prefixes_insert_trigger(); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.delete_leaf_prefixes(); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.get_level(); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.get_prefixes(); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.delete_prefix(_bucket_id text, _name text); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.add_prefixes(_bucket_id text, _name text); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.get_prefix(); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.objects_update_level_trigger(); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.objects_update_prefix_trigger(); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.lock_top_prefixes(bucket_ids text[], names text[]); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; + +DO $$ +BEGIN + DROP FUNCTION IF EXISTS storage.search_v1_optimised(prefix text, bucketname text, limits integer, levels integer, offsets integer, search text, sortcolumn text, sortorder text); +EXCEPTION WHEN OTHERS THEN NULL; +END; +$$; diff --git a/migrations/tenant/0053-drop-index-lower-name.sql b/migrations/tenant/0053-drop-index-lower-name.sql new file mode 100644 index 000000000..88d90a802 --- /dev/null +++ b/migrations/tenant/0053-drop-index-lower-name.sql @@ -0,0 +1,2 @@ +-- postgres-migrations disable-transaction +DROP INDEX CONCURRENTLY IF EXISTS idx_objects_lower_name; \ No newline at end of file diff --git a/migrations/tenant/0054-drop-index-object-level.sql b/migrations/tenant/0054-drop-index-object-level.sql new file mode 100644 index 000000000..7c8ef75e4 --- /dev/null +++ b/migrations/tenant/0054-drop-index-object-level.sql @@ -0,0 +1,2 @@ +-- postgres-migrations disable-transaction +DROP INDEX CONCURRENTLY IF EXISTS objects_bucket_id_level_idx; diff --git a/migrations/tenant/0055-prevent-direct-deletes.sql b/migrations/tenant/0055-prevent-direct-deletes.sql new file mode 100644 index 000000000..15119ffe6 --- /dev/null +++ b/migrations/tenant/0055-prevent-direct-deletes.sql @@ -0,0 +1,27 @@ +-- Create a function that prevents direct deletes unless storage.allow_delete_query is set +CREATE OR REPLACE FUNCTION storage.protect_delete() +RETURNS TRIGGER +LANGUAGE plpgsql +AS $$ +BEGIN + -- Check if storage.allow_delete_query is set to 'true' + IF COALESCE(current_setting('storage.allow_delete_query', true), 'false') != 'true' THEN + RAISE EXCEPTION 'Direct deletion from storage tables is not allowed. Use the Storage API instead.' + USING HINT = 'This prevents accidental data loss from orphaned objects.', + ERRCODE = '42501'; + END IF; + RETURN NULL; +END; +$$; + +DROP TRIGGER IF EXISTS protect_buckets_delete ON storage.buckets; +CREATE TRIGGER protect_buckets_delete + BEFORE DELETE ON storage.buckets + FOR EACH STATEMENT + EXECUTE FUNCTION storage.protect_delete(); + +DROP TRIGGER IF EXISTS protect_objects_delete ON storage.objects; +CREATE TRIGGER protect_objects_delete + BEFORE DELETE ON storage.objects + FOR EACH STATEMENT + EXECUTE FUNCTION storage.protect_delete(); diff --git a/migrations/tenant/0057-s3-multipart-uploads-metadata.sql b/migrations/tenant/0057-s3-multipart-uploads-metadata.sql new file mode 100644 index 000000000..ef9496bb8 --- /dev/null +++ b/migrations/tenant/0057-s3-multipart-uploads-metadata.sql @@ -0,0 +1 @@ +ALTER TABLE storage.s3_multipart_uploads ADD COLUMN IF NOT EXISTS metadata jsonb NULL; \ No newline at end of file diff --git a/migrations/tenant/0058-operation-ergonomics.sql b/migrations/tenant/0058-operation-ergonomics.sql new file mode 100644 index 000000000..61b4d6909 --- /dev/null +++ b/migrations/tenant/0058-operation-ergonomics.sql @@ -0,0 +1,57 @@ +-- Ergonomic helpers for operation-aware RLS policies. +-- These helpers read the existing transaction-local storage.operation GUC (Grand Unified Configuration). + +CREATE OR REPLACE FUNCTION storage.allow_only_operation(expected_operation text) +RETURNS boolean +LANGUAGE sql +STABLE +AS $$ + WITH current_operation AS ( + SELECT storage.operation() AS raw_operation + ), + normalized AS ( + SELECT + CASE + WHEN raw_operation LIKE 'storage.%' THEN substr(raw_operation, 9) + ELSE raw_operation + END AS current_operation, + CASE + WHEN expected_operation LIKE 'storage.%' THEN substr(expected_operation, 9) + ELSE expected_operation + END AS requested_operation + FROM current_operation + ) + SELECT CASE + WHEN requested_operation IS NULL OR requested_operation = '' THEN FALSE + ELSE COALESCE(current_operation = requested_operation, FALSE) + END + FROM normalized; +$$; + +CREATE OR REPLACE FUNCTION storage.allow_any_operation(expected_operations text[]) +RETURNS boolean +LANGUAGE sql +STABLE +AS $$ + WITH current_operation AS ( + SELECT storage.operation() AS raw_operation + ), + normalized AS ( + SELECT CASE + WHEN raw_operation LIKE 'storage.%' THEN substr(raw_operation, 9) + ELSE raw_operation + END AS current_operation + FROM current_operation + ) + SELECT EXISTS ( + SELECT 1 + FROM normalized n + CROSS JOIN LATERAL unnest(expected_operations) AS expected_operation + WHERE expected_operation IS NOT NULL + AND expected_operation <> '' + AND n.current_operation = CASE + WHEN expected_operation LIKE 'storage.%' THEN substr(expected_operation, 9) + ELSE expected_operation + END + ); +$$; diff --git a/migrations/tenant/56-fix-optimized-search-function.sql b/migrations/tenant/56-fix-optimized-search-function.sql new file mode 100644 index 000000000..2af963cce --- /dev/null +++ b/migrations/tenant/56-fix-optimized-search-function.sql @@ -0,0 +1,277 @@ + +-- fixes issue where prefixes are returned in lowercase for search v1 +-- ============================================================================ +-- search: Legacy function with offset-based pagination using hybrid skip-scan +-- ============================================================================ +-- Maintains backwards compatibility with the original search function signature. +-- Uses HYBRID approach for optimal performance: +-- 1. STATIC SQL peek for folder discovery (plan cached, very fast) +-- 2. DYNAMIC SQL batch for files (overhead amortized over many rows) +-- Falls back to path_tokens approach for non-name sorting. +-- ============================================================================ +CREATE OR REPLACE FUNCTION storage.search( + prefix text, + bucketname text, + limits int DEFAULT 100, + levels int DEFAULT 1, + offsets int DEFAULT 0, + search text DEFAULT '', + sortcolumn text DEFAULT 'name', + sortorder text DEFAULT 'asc' +) +RETURNS TABLE ( + name text, + id uuid, + updated_at timestamptz, + created_at timestamptz, + last_accessed_at timestamptz, + metadata jsonb +) +SECURITY INVOKER +LANGUAGE plpgsql STABLE +AS $func$ +DECLARE + v_peek_name TEXT; + v_current RECORD; + v_common_prefix TEXT; + v_delimiter CONSTANT TEXT := '/'; + + -- Configuration + v_limit INT; + v_prefix TEXT; + v_prefix_lower TEXT; + v_is_asc BOOLEAN; + v_order_by TEXT; + v_sort_order TEXT; + v_upper_bound TEXT; + v_file_batch_size INT; + + -- Dynamic SQL for batch query only + v_batch_query TEXT; + + -- Seek state + v_next_seek TEXT; + v_count INT := 0; + v_skipped INT := 0; +BEGIN + -- ======================================================================== + -- INITIALIZATION + -- ======================================================================== + v_limit := LEAST(coalesce(limits, 100), 1500); + v_prefix := coalesce(prefix, '') || coalesce(search, ''); + v_prefix_lower := lower(v_prefix); + v_is_asc := lower(coalesce(sortorder, 'asc')) = 'asc'; + v_file_batch_size := LEAST(GREATEST(v_limit * 2, 100), 1000); + + -- Validate sort column + CASE lower(coalesce(sortcolumn, 'name')) + WHEN 'name' THEN v_order_by := 'name'; + WHEN 'updated_at' THEN v_order_by := 'updated_at'; + WHEN 'created_at' THEN v_order_by := 'created_at'; + WHEN 'last_accessed_at' THEN v_order_by := 'last_accessed_at'; + ELSE v_order_by := 'name'; + END CASE; + + v_sort_order := CASE WHEN v_is_asc THEN 'asc' ELSE 'desc' END; + + -- ======================================================================== + -- NON-NAME SORTING: Use path_tokens approach (unchanged) + -- ======================================================================== + IF v_order_by != 'name' THEN + RETURN QUERY EXECUTE format( + $sql$ + WITH folders AS ( + SELECT path_tokens[$1] AS folder + FROM storage.objects + WHERE objects.name ILIKE $2 || '%%' + AND bucket_id = $3 + AND array_length(objects.path_tokens, 1) <> $1 + GROUP BY folder + ORDER BY folder %s + ) + (SELECT folder AS "name", + NULL::uuid AS id, + NULL::timestamptz AS updated_at, + NULL::timestamptz AS created_at, + NULL::timestamptz AS last_accessed_at, + NULL::jsonb AS metadata FROM folders) + UNION ALL + (SELECT path_tokens[$1] AS "name", + id, updated_at, created_at, last_accessed_at, metadata + FROM storage.objects + WHERE objects.name ILIKE $2 || '%%' + AND bucket_id = $3 + AND array_length(objects.path_tokens, 1) = $1 + ORDER BY %I %s) + LIMIT $4 OFFSET $5 + $sql$, v_sort_order, v_order_by, v_sort_order + ) USING levels, v_prefix, bucketname, v_limit, offsets; + RETURN; + END IF; + + -- ======================================================================== + -- NAME SORTING: Hybrid skip-scan with batch optimization + -- ======================================================================== + + -- Calculate upper bound for prefix filtering + IF v_prefix_lower = '' THEN + v_upper_bound := NULL; + ELSIF right(v_prefix_lower, 1) = v_delimiter THEN + v_upper_bound := left(v_prefix_lower, -1) || chr(ascii(v_delimiter) + 1); + ELSE + v_upper_bound := left(v_prefix_lower, -1) || chr(ascii(right(v_prefix_lower, 1)) + 1); + END IF; + + -- Build batch query (dynamic SQL - called infrequently, amortized over many rows) + IF v_is_asc THEN + IF v_upper_bound IS NOT NULL THEN + v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' || + 'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" >= $2 ' || + 'AND lower(o.name) COLLATE "C" < $3 ORDER BY lower(o.name) COLLATE "C" ASC LIMIT $4'; + ELSE + v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' || + 'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" >= $2 ' || + 'ORDER BY lower(o.name) COLLATE "C" ASC LIMIT $4'; + END IF; + ELSE + IF v_upper_bound IS NOT NULL THEN + v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' || + 'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" < $2 ' || + 'AND lower(o.name) COLLATE "C" >= $3 ORDER BY lower(o.name) COLLATE "C" DESC LIMIT $4'; + ELSE + v_batch_query := 'SELECT o.name, o.id, o.updated_at, o.created_at, o.last_accessed_at, o.metadata ' || + 'FROM storage.objects o WHERE o.bucket_id = $1 AND lower(o.name) COLLATE "C" < $2 ' || + 'ORDER BY lower(o.name) COLLATE "C" DESC LIMIT $4'; + END IF; + END IF; + + -- Initialize seek position + IF v_is_asc THEN + v_next_seek := v_prefix_lower; + ELSE + -- DESC: find the last item in range first (static SQL) + IF v_upper_bound IS NOT NULL THEN + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_prefix_lower AND lower(o.name) COLLATE "C" < v_upper_bound + ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1; + ELSIF v_prefix_lower <> '' THEN + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_prefix_lower + ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1; + ELSE + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname + ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1; + END IF; + + IF v_peek_name IS NOT NULL THEN + v_next_seek := lower(v_peek_name) || v_delimiter; + ELSE + RETURN; + END IF; + END IF; + + -- ======================================================================== + -- MAIN LOOP: Hybrid peek-then-batch algorithm + -- Uses STATIC SQL for peek (hot path) and DYNAMIC SQL for batch + -- ======================================================================== + LOOP + EXIT WHEN v_count >= v_limit; + + -- STEP 1: PEEK using STATIC SQL (plan cached, very fast) + IF v_is_asc THEN + IF v_upper_bound IS NOT NULL THEN + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_next_seek AND lower(o.name) COLLATE "C" < v_upper_bound + ORDER BY lower(o.name) COLLATE "C" ASC LIMIT 1; + ELSE + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" >= v_next_seek + ORDER BY lower(o.name) COLLATE "C" ASC LIMIT 1; + END IF; + ELSE + IF v_upper_bound IS NOT NULL THEN + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" < v_next_seek AND lower(o.name) COLLATE "C" >= v_prefix_lower + ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1; + ELSIF v_prefix_lower <> '' THEN + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" < v_next_seek AND lower(o.name) COLLATE "C" >= v_prefix_lower + ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1; + ELSE + SELECT o.name INTO v_peek_name FROM storage.objects o + WHERE o.bucket_id = bucketname AND lower(o.name) COLLATE "C" < v_next_seek + ORDER BY lower(o.name) COLLATE "C" DESC LIMIT 1; + END IF; + END IF; + + EXIT WHEN v_peek_name IS NULL; + + -- STEP 2: Check if this is a FOLDER or FILE + v_common_prefix := storage.get_common_prefix(lower(v_peek_name), v_prefix_lower, v_delimiter); + + IF v_common_prefix IS NOT NULL THEN + -- FOLDER: Handle offset, emit if needed, skip to next folder + IF v_skipped < offsets THEN + v_skipped := v_skipped + 1; + ELSE + name := split_part(rtrim(storage.get_common_prefix(v_peek_name, v_prefix, v_delimiter), v_delimiter), v_delimiter, levels); + id := NULL; + updated_at := NULL; + created_at := NULL; + last_accessed_at := NULL; + metadata := NULL; + RETURN NEXT; + v_count := v_count + 1; + END IF; + + -- Advance seek past the folder range + IF v_is_asc THEN + v_next_seek := lower(left(v_common_prefix, -1)) || chr(ascii(v_delimiter) + 1); + ELSE + v_next_seek := lower(v_common_prefix); + END IF; + ELSE + -- FILE: Batch fetch using DYNAMIC SQL (overhead amortized over many rows) + -- For ASC: upper_bound is the exclusive upper limit (< condition) + -- For DESC: prefix_lower is the inclusive lower limit (>= condition) + FOR v_current IN EXECUTE v_batch_query + USING bucketname, v_next_seek, + CASE WHEN v_is_asc THEN COALESCE(v_upper_bound, v_prefix_lower) ELSE v_prefix_lower END, v_file_batch_size + LOOP + v_common_prefix := storage.get_common_prefix(lower(v_current.name), v_prefix_lower, v_delimiter); + + IF v_common_prefix IS NOT NULL THEN + -- Hit a folder: exit batch, let peek handle it + v_next_seek := lower(v_current.name); + EXIT; + END IF; + + -- Handle offset skipping + IF v_skipped < offsets THEN + v_skipped := v_skipped + 1; + ELSE + -- Emit file + name := split_part(v_current.name, v_delimiter, levels); + id := v_current.id; + updated_at := v_current.updated_at; + created_at := v_current.created_at; + last_accessed_at := v_current.last_accessed_at; + metadata := v_current.metadata; + RETURN NEXT; + v_count := v_count + 1; + END IF; + + -- Advance seek past this file + IF v_is_asc THEN + v_next_seek := lower(v_current.name) || v_delimiter; + ELSE + v_next_seek := lower(v_current.name); + END IF; + + EXIT WHEN v_count >= v_limit; + END LOOP; + END IF; + END LOOP; +END; +$func$; \ No newline at end of file diff --git a/monitoring/grafana/config/dashboards/default.yml b/monitoring/grafana/config/dashboards/default.yml index a56cd4425..11b9a57bc 100644 --- a/monitoring/grafana/config/dashboards/default.yml +++ b/monitoring/grafana/config/dashboards/default.yml @@ -2,9 +2,9 @@ apiVersion: 1 providers: # an unique provider name. Required - - name: 'Default Dashboard' + - name: "Default Dashboard" # name of the dashboard folder. - folder: 'Storage' + folder: "Storage" type: file disableDeletion: false # how often Grafana will scan for changed dashboards @@ -13,4 +13,4 @@ providers: options: # path to dashboard files on disk. Required when using the 'file' type path: /var/lib/grafana/dashboards - foldersFromFilesStructure: true \ No newline at end of file + foldersFromFilesStructure: true diff --git a/monitoring/grafana/config/datasources/datacourse.yml b/monitoring/grafana/config/datasources/datacourse.yml index 79c7868b7..1384ce1c2 100644 --- a/monitoring/grafana/config/datasources/datacourse.yml +++ b/monitoring/grafana/config/datasources/datacourse.yml @@ -7,4 +7,4 @@ datasources: isDefault: true access: proxy editable: true - uid: local_prometheus \ No newline at end of file + uid: local_prometheus diff --git a/monitoring/grafana/dashboards/postgres.json b/monitoring/grafana/dashboards/postgres.json index 91b7b764a..e7ac86e61 100644 --- a/monitoring/grafana/dashboards/postgres.json +++ b/monitoring/grafana/dashboards/postgres.json @@ -84,15 +84,11 @@ "format": "time_series", "groupBy": [ { - "params": [ - "$interval" - ], + "params": ["$interval"], "type": "time" }, { - "params": [ - "null" - ], + "params": ["null"], "type": "fill" } ], @@ -105,9 +101,7 @@ "select": [ [ { - "params": [ - "tup_fetched" - ], + "params": ["tup_fetched"], "type": "field" }, { @@ -115,9 +109,7 @@ "type": "mean" }, { - "params": [ - "10s" - ], + "params": ["10s"], "type": "non_negative_derivative" } ] @@ -138,15 +130,11 @@ "format": "time_series", "groupBy": [ { - "params": [ - "$interval" - ], + "params": ["$interval"], "type": "time" }, { - "params": [ - "null" - ], + "params": ["null"], "type": "fill" } ], @@ -159,9 +147,7 @@ "select": [ [ { - "params": [ - "tup_fetched" - ], + "params": ["tup_fetched"], "type": "field" }, { @@ -169,9 +155,7 @@ "type": "mean" }, { - "params": [ - "10s" - ], + "params": ["10s"], "type": "non_negative_derivative" } ] @@ -192,15 +176,11 @@ "format": "time_series", "groupBy": [ { - "params": [ - "$interval" - ], + "params": ["$interval"], "type": "time" }, { - "params": [ - "null" - ], + "params": ["null"], "type": "fill" } ], @@ -213,9 +193,7 @@ "select": [ [ { - "params": [ - "tup_fetched" - ], + "params": ["tup_fetched"], "type": "field" }, { @@ -223,9 +201,7 @@ "type": "mean" }, { - "params": [ - "10s" - ], + "params": ["10s"], "type": "non_negative_derivative" } ] @@ -246,15 +222,11 @@ "format": "time_series", "groupBy": [ { - "params": [ - "$interval" - ], + "params": ["$interval"], "type": "time" }, { - "params": [ - "null" - ], + "params": ["null"], "type": "fill" } ], @@ -267,9 +239,7 @@ "select": [ [ { - "params": [ - "tup_fetched" - ], + "params": ["tup_fetched"], "type": "field" }, { @@ -277,9 +247,7 @@ "type": "mean" }, { - "params": [ - "10s" - ], + "params": ["10s"], "type": "non_negative_derivative" } ] @@ -300,15 +268,11 @@ "format": "time_series", "groupBy": [ { - "params": [ - "$interval" - ], + "params": ["$interval"], "type": "time" }, { - "params": [ - "null" - ], + "params": ["null"], "type": "fill" } ], @@ -321,9 +285,7 @@ "select": [ [ { - "params": [ - "tup_fetched" - ], + "params": ["tup_fetched"], "type": "field" }, { @@ -331,9 +293,7 @@ "type": "mean" }, { - "params": [ - "10s" - ], + "params": ["10s"], "type": "non_negative_derivative" } ] @@ -394,11 +354,7 @@ "cacheTimeout": null, "colorBackground": false, "colorValue": false, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], + "colors": ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], "datasource": "$datasource", "decimals": 0, "editable": true, @@ -467,15 +423,11 @@ "format": "time_series", "groupBy": [ { - "params": [ - "$interval" - ], + "params": ["$interval"], "type": "time" }, { - "params": [ - "null" - ], + "params": ["null"], "type": "fill" } ], @@ -487,9 +439,7 @@ "select": [ [ { - "params": [ - "xact_commit" - ], + "params": ["xact_commit"], "type": "field" }, { @@ -497,9 +447,7 @@ "type": "mean" }, { - "params": [ - "10s" - ], + "params": ["10s"], "type": "non_negative_derivative" } ] @@ -591,15 +539,11 @@ "format": "time_series", "groupBy": [ { - "params": [ - "$interval" - ], + "params": ["$interval"], "type": "time" }, { - "params": [ - "null" - ], + "params": ["null"], "type": "fill" } ], @@ -612,9 +556,7 @@ "select": [ [ { - "params": [ - "buffers_alloc" - ], + "params": ["buffers_alloc"], "type": "field" }, { @@ -643,15 +585,11 @@ "format": "time_series", "groupBy": [ { - "params": [ - "$interval" - ], + "params": ["$interval"], "type": "time" }, { - "params": [ - "null" - ], + "params": ["null"], "type": "fill" } ], @@ -664,9 +602,7 @@ "select": [ [ { - "params": [ - "buffers_alloc" - ], + "params": ["buffers_alloc"], "type": "field" }, { @@ -695,15 +631,11 @@ "format": "time_series", "groupBy": [ { - "params": [ - "$interval" - ], + "params": ["$interval"], "type": "time" }, { - "params": [ - "null" - ], + "params": ["null"], "type": "fill" } ], @@ -716,9 +648,7 @@ "select": [ [ { - "params": [ - "buffers_alloc" - ], + "params": ["buffers_alloc"], "type": "field" }, { @@ -747,15 +677,11 @@ "format": "time_series", "groupBy": [ { - "params": [ - "$interval" - ], + "params": ["$interval"], "type": "time" }, { - "params": [ - "null" - ], + "params": ["null"], "type": "fill" } ], @@ -768,9 +694,7 @@ "select": [ [ { - "params": [ - "buffers_alloc" - ], + "params": ["buffers_alloc"], "type": "field" }, { @@ -799,15 +723,11 @@ "format": "time_series", "groupBy": [ { - "params": [ - "$interval" - ], + "params": ["$interval"], "type": "time" }, { - "params": [ - "null" - ], + "params": ["null"], "type": "fill" } ], @@ -820,9 +740,7 @@ "select": [ [ { - "params": [ - "buffers_alloc" - ], + "params": ["buffers_alloc"], "type": "field" }, { @@ -946,15 +864,11 @@ "format": "time_series", "groupBy": [ { - "params": [ - "$interval" - ], + "params": ["$interval"], "type": "time" }, { - "params": [ - "null" - ], + "params": ["null"], "type": "fill" } ], @@ -967,9 +881,7 @@ "select": [ [ { - "params": [ - "conflicts" - ], + "params": ["conflicts"], "type": "field" }, { @@ -998,15 +910,11 @@ "format": "time_series", "groupBy": [ { - "params": [ - "$interval" - ], + "params": ["$interval"], "type": "time" }, { - "params": [ - "null" - ], + "params": ["null"], "type": "fill" } ], @@ -1019,9 +927,7 @@ "select": [ [ { - "params": [ - "deadlocks" - ], + "params": ["deadlocks"], "type": "field" }, { @@ -1296,9 +1202,7 @@ "refresh": false, "schemaVersion": 26, "style": "dark", - "tags": [ - "postgres" - ], + "tags": ["postgres"], "templating": { "list": [ { @@ -1384,32 +1288,11 @@ "to": "now" }, "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], + "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] }, "timezone": "browser", "title": "Postgres Overview", "uid": "wGgaPlciz", "version": 5 -} \ No newline at end of file +} diff --git a/monitoring/grafana/dashboards/storage-otel.json b/monitoring/grafana/dashboards/storage-otel.json new file mode 100644 index 000000000..7aa21bfe4 --- /dev/null +++ b/monitoring/grafana/dashboards/storage-otel.json @@ -0,0 +1,6100 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Storage API OTel Metrics Dashboard with tenant and region support", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": 2, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "panels": [], + "title": "Overview", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Application Performance Index score based on request latency. Score >= 90% is good (green), 70-90% needs attention (orange), < 70% is poor (red). Target and tolerated thresholds are configurable.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#C4162A", + "value": 0 + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 70 + }, + { + "color": "#299c46", + "value": 90 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 5, + "x": 0, + "y": 1 + }, + "id": 200, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "((sum(storage_api_otel_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\", le=\"0.3\",status_code=~\"2..\"}) + (sum(storage_api_otel_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\", le=\"1.2\",status_code=~\"2..\"}) - sum(storage_api_otel_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\", le=\"0.3\",status_code=~\"2..\"})) / 2) / sum(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"2..\"})) * 100", + "legendFormat": "score", + "refId": "A" + } + ], + "title": "Apdex Score", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Current queries per second (QPS). Shows the real-time request rate across all endpoints.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#299c46", + "value": 0 + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 100 + }, + { + "color": "#C4162A", + "value": 200 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 3, + "x": 5, + "y": 1 + }, + "id": 201, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["max"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(rate(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", + "legendFormat": "", + "refId": "A" + } + ], + "title": "QPS", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Total number of HTTP requests processed during the selected time range.", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "rgb(31, 120, 193)", + "mode": "fixed" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 3, + "x": 8, + "y": 1 + }, + "id": 202, + "options": { + "colorMode": "none", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(increase(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\"}[$__range]))", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Requests", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Process restart count during the selected time range. High restart counts may indicate crashes or OOM issues.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#299c46", + "value": 0 + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 1 + }, + { + "color": "#C4162A", + "value": 2 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 3, + "x": 11, + "y": 1 + }, + "id": 203, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(changes(storage_api_otel_process_start_time_seconds{region=~\"$region\", instance=~\"$instance\"}[$__range]))", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Restarts", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Server error rate (5xx) as a percentage of total requests. Values above 1% require investigation.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#299c46", + "value": 0 + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 5 + }, + { + "color": "#C4162A", + "value": 10 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 3, + "x": 14, + "y": 1 + }, + "id": 204, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "(sum(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"5..\"} OR on() vector(0)) / sum(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\"}))*100", + "legendFormat": "error %", + "refId": "A" + } + ], + "title": "5xx Errors", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Client error rate (4xx) as a percentage of total requests. High rates may indicate API misuse or client bugs.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#299c46", + "value": 0 + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 10 + }, + { + "color": "#C4162A", + "value": 20 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 3, + "x": 17, + "y": 1 + }, + "id": 205, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "(sum(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"4..\"} OR on() vector(0)) / sum(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\"}))*100", + "legendFormat": "error %", + "refId": "A" + } + ], + "title": "4xx Errors", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "File upload statistics: started, completed, multipart, and standard uploads during the selected time range.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 4, + "x": 20, + "y": 1 + }, + "id": 206, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(increase(storage_api_otel_upload_started_total{region=~\"$region\", instance=~\"$instance\"}[$__range]))", + "legendFormat": "Started", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(increase(storage_api_otel_upload_success_total{region=~\"$region\", instance=~\"$instance\"}[$__range]))", + "legendFormat": "Completed", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(increase(storage_api_otel_upload_started_total{region=~\"$region\", instance=~\"$instance\"}[$__range]))", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "C" + } + ], + "title": "File Uploads", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Current CPU usage as a percentage. Values above 75% indicate high CPU load, above 90% is critical.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "orange", + "value": 75 + }, + { + "color": "red", + "value": 90 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 3 + }, + "id": 207, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "avg(rate(storage_api_otel_process_cpu_seconds_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", + "legendFormat": "Avg", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "max(rate(storage_api_otel_process_cpu_seconds_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", + "hide": false, + "instant": false, + "legendFormat": "Max", + "range": true, + "refId": "B" + } + ], + "title": "CPU", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Current RAM usage as a percentage of 2GB limit. Monitor for memory pressure and potential OOM issues.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 0.8 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 3 + }, + "id": 209, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "avg((storage_api_otel_process_memory_usage{region=~\"$region\", instance=~\"$instance\"}) / (2*1024*1024*1024))", + "legendFormat": "Avg", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "max(storage_api_otel_process_resident_memory_bytes{region=~\"$region\", instance=~\"$instance\"}) / (2*1024*1024*1024)", + "hide": false, + "instant": false, + "legendFormat": "Max", + "range": true, + "refId": "B" + } + ], + "title": "RAM", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Event loop utilization ratio (0-1). Values close to 1 indicate the event loop is saturated.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "orange", + "value": 0.7 + }, + { + "color": "red", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 12, + "y": 3 + }, + "id": 212, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "storage_api_otel_nodejs_eventloop_utilization_ratio{region=~\"$region\", instance=~\"$instance\"}", + "range": true, + "refId": "A" + } + ], + "title": "Event Loop", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Response counts grouped by HTTP status code category (2xx success, 4xx client errors, 5xx server errors).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "dark-blue", + "value": 0 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 15, + "y": 3 + }, + "id": 211, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "vertical", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"2..\"}) or vector(0)", + "legendFormat": "2xx", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"4..\"}) or vector(0)", + "legendFormat": "4xx", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"5..\"}) or vector(0)", + "legendFormat": "5xx", + "refId": "C" + } + ], + "title": "Responses By Code", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Active database connection pools. High counts may indicate connection pool pressure.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "orange", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 20, + "y": 3 + }, + "id": 213, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_otel_db_pool{region=~\"$region\", instance=~\"$instance\"})", + "legendFormat": "", + "refId": "A" + } + ], + "title": "DB Pools", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Pending queue jobs across all job types. Growing queues indicate processing bottlenecks.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "orange", + "value": 100 + }, + { + "color": "red", + "value": 500 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 0, + "y": 7 + }, + "id": 214, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_otel_queue_job_scheduled{region=~\"$region\", instance=~\"$instance\"})", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Queue Jobs", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 11 + }, + "id": 215, + "panels": [], + "title": "Requests Overview", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "HTTP requests per second broken down by status code category. Shows total requests along with 2xx (success), 4xx (client errors), and 5xx (server errors) rates. Use this to monitor overall API traffic and identify error spikes.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 12 + }, + "id": 2, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", + "legendFormat": "Total", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"2xx\"}[$__rate_interval]))", + "legendFormat": "2xx", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"4xx\"}[$__rate_interval]))", + "legendFormat": "4xx", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"5xx\"}[$__rate_interval]))", + "legendFormat": "5xx", + "range": true, + "refId": "D" + } + ], + "title": "Request Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "HTTP request duration percentiles (p50, p90, p99). P50 shows median latency, p90 shows latency for 90% of requests, and p99 captures tail latency. High p99 values may indicate slow database queries, external service delays, or resource contention.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 12 + }, + "id": 3, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "histogram_quantile(0.50, sum(rate(storage_api_otel_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le))", + "legendFormat": "p50", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "histogram_quantile(0.90, sum(rate(storage_api_otel_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le))", + "legendFormat": "p90", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "histogram_quantile(0.99, sum(rate(storage_api_otel_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le))", + "legendFormat": "p99", + "refId": "C" + } + ], + "title": "Request Latency (Percentiles)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Server error rate as a percentage of total requests. Values above 1% may indicate service issues requiring investigation. Common causes include database connection failures, S3 errors, or application exceptions.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 12 + }, + "id": 4, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"5xx\"}[$__rate_interval])) / sum(rate(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", + "legendFormat": "Error Rate", + "range": true, + "refId": "A" + } + ], + "title": "Error Rate (5xx)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Network transfer rates based on HTTP Content-Length headers. Transfer In shows data received from clients (uploads, POST bodies). Transfer Out shows data sent to clients (downloads, responses). Useful for monitoring bandwidth usage and identifying large file transfers.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "Bps" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Transfer In" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Transfer Out" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 20 + }, + "id": 216, + "options": { + "legend": { + "calcs": ["mean", "max", "sum"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_http_request_size_bytes_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", + "legendFormat": "Transfer In", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_http_response_size_bytes_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", + "legendFormat": "Transfer Out", + "range": true, + "refId": "B" + } + ], + "title": "Transfer In/Out", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Top storage operations by request count. Shows which operations receive the most traffic. Use this to identify hot paths, optimize frequently accessed code paths, and understand API usage patterns.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "footer": { + "reducers": [] + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "operation" + }, + "properties": [ + { + "id": "custom.width", + "value": 300 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value" + }, + "properties": [ + { + "id": "displayName", + "value": "Requests/sec" + }, + { + "id": "unit", + "value": "reqps" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 20 + }, + "id": 217, + "options": { + "cellHeight": "sm", + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Value" + } + ] + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "topk(10, sum by (operation) (rate(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])))", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "Requests by Operation", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": {} + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Error count over time broken down by status code. Shows 4xx client errors and 5xx server errors separately to help distinguish between client-side issues and server-side problems.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "4xx Errors" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "5xx Errors" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 28 + }, + "id": 218, + "options": { + "legend": { + "calcs": ["mean", "max", "sum"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"4xx\"}[$__rate_interval]))", + "legendFormat": "4xx Errors", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"5xx\"}[$__rate_interval]))", + "legendFormat": "5xx Errors", + "range": true, + "refId": "B" + } + ], + "title": "Errors Over Time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Top storage operations by error count. Shows which operations are generating the most errors. Use this to identify problematic code paths that need attention or debugging.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "footer": { + "reducers": [] + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "orange", + "value": 1 + }, + { + "color": "red", + "value": 10 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "operation" + }, + "properties": [ + { + "id": "custom.width", + "value": 300 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value" + }, + "properties": [ + { + "id": "displayName", + "value": "Errors/sec" + }, + { + "id": "unit", + "value": "short" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 28 + }, + "id": 219, + "options": { + "cellHeight": "sm", + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Value" + } + ] + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "topk(10, sum by (operation) (rate(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"4xx|5xx\"}[$__rate_interval])))", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "Errors by Operation", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": {} + } + } + ], + "type": "table" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 36 + }, + "id": 10, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "HTTP requests per second grouped by tenant ID. Use this to identify high-traffic tenants, detect unusual activity patterns, or investigate tenant-specific issues. Filter by tenant using the dropdown above.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 37 + }, + "id": 11, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", tenantId=~\"$tenant\"}[$__rate_interval])) by (tenantId)", + "legendFormat": "{{ tenantId }}", + "range": true, + "refId": "A" + } + ], + "title": "Request Rate by Tenant", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "95th percentile request latency per tenant. Helps identify tenants experiencing performance issues due to large files, complex queries, or resource-intensive operations. Compare against baseline to detect anomalies.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 37 + }, + "id": 12, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "histogram_quantile(0.95, sum(rate(storage_api_otel_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\", tenantId=~\"$tenant\"}[$__rate_interval])) by (le, tenantId))", + "legendFormat": "{{ tenantId }}", + "refId": "A" + } + ], + "title": "P95 Latency by Tenant", + "type": "timeseries" + } + ], + "title": "HTTP Requests by Tenant", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 37 + }, + "id": 20, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "The 10 most frequently called API endpoints by requests per second. Use this to understand API usage patterns, identify heavily-used endpoints for optimization, and detect unexpected traffic patterns.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 46 + }, + "id": 21, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "topk(10, sum(rate(storage_api_otel_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (operation))", + "legendFormat": "{{ operation }}", + "refId": "A" + } + ], + "title": "Top 10 Operations by Request Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "The 10 slowest storage operations by 95th percentile latency. These operations are prime candidates for performance optimization. High latency may indicate complex database operations, large file transfers, or inefficient code paths.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 46 + }, + "id": 22, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "topk(10, histogram_quantile(0.95, sum(rate(storage_api_otel_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le, operation)))", + "legendFormat": "{{ operation }}", + "range": true, + "refId": "A" + } + ], + "title": "Top 10 Slowest Operations (P95)", + "type": "timeseries" + } + ], + "title": "HTTP Requests by Operation", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 38 + }, + "id": 30, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Comparison of started uploads versus successfully completed uploads. A growing gap between these values may indicate upload failures, timeouts, or client disconnections. Use this to monitor upload reliability.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 95 + }, + "id": 31, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_otel_upload_started_total{region=~\"$region\", instance=~\"$instance\"})", + "legendFormat": "Started", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_otel_upload_success_total{region=~\"$region\", instance=~\"$instance\"})", + "legendFormat": "Success", + "range": true, + "refId": "B" + } + ], + "title": "Uploads (Started vs Success)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Successful uploads categorized by upload method: standard (single request), resumable (TUS protocol), S3 (direct S3 upload), and multipart. Helps understand which upload mechanisms are most used and their success rates.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 95 + }, + "id": 32, + "options": { + "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_otel_upload_success_total{region=~\"$region\", instance=~\"$instance\", is_standard=\"1\"})", + "legendFormat": "Standard", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_otel_upload_success_total{region=~\"$region\", instance=~\"$instance\", is_resumable=\"1\"})", + "legendFormat": "Resumable (TUS)", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_otel_upload_success_total{region=~\"$region\", instance=~\"$instance\", is_s3=\"1\"})", + "legendFormat": "S3", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_otel_upload_success_total{region=~\"$region\", instance=~\"$instance\", is_multipart=\"1\"})", + "legendFormat": "Multipart", + "range": true, + "refId": "D" + } + ], + "title": "Uploads by Type", + "type": "bargauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Successful uploads grouped by tenant ID. Use this to identify high-volume uploaders, track tenant activity, and investigate tenant-specific upload issues.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 95 + }, + "id": 33, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_otel_upload_success_total{region=~\"$region\", instance=~\"$instance\", tenantId=~\"$tenant\"}) by (tenantId)", + "legendFormat": "{{ tenantId }}", + "range": true, + "refId": "A" + } + ], + "title": "Uploads by Tenant", + "type": "timeseries" + } + ], + "title": "Uploads", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 39 + }, + "id": 40, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Database query latency percentiles (p50, p95) grouped by operation type. Identifies slow queries that may need optimization through indexing, query rewriting, or connection pool tuning. Monitor for performance regressions.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 104 + }, + "id": 41, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "histogram_quantile(0.50, sum(rate(storage_api_otel_database_query_performance_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le, name))", + "legendFormat": "p50 {{ name }}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "histogram_quantile(0.95, sum(rate(storage_api_otel_database_query_performance_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le, name))", + "legendFormat": "p95 {{ name }}", + "refId": "B" + } + ], + "title": "Database Query Performance by Operation", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Active database connection pool count. High values may indicate connection leaks or pool exhaustion. Monitor this alongside query performance to ensure adequate connection availability.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 104 + }, + "id": 42, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_otel_db_pool{region=~\"$region\", instance=~\"$instance\"})", + "legendFormat": "Active Pools", + "range": true, + "refId": "A" + } + ], + "title": "Database Pools", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Database connections categorized by type (internal vs external). External connections are from external services. Monitor for connection growth patterns and potential connection exhaustion.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 104 + }, + "id": 43, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_otel_db_connections{region=~\"$region\", instance=~\"$instance\"}) by (is_external)", + "legendFormat": "External: {{ is_external }}", + "refId": "A" + } + ], + "title": "Database Connections", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Rate of database queries per second grouped by operation type. Use this to monitor database load, identify query spikes, and correlate with application activity. High query rates may indicate opportunities for caching or query optimization.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 112 + }, + "id": 220, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(rate(storage_api_otel_database_query_performance_count{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (name)", + "legendFormat": "{{ name }}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(rate(storage_api_otel_database_query_performance_count{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", + "legendFormat": "Total", + "refId": "B" + } + ], + "title": "Database Query Rate", + "type": "timeseries" + } + ], + "title": "Database", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 40 + }, + "id": 45, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Rate of monitored cache lookups grouped by cache name and outcome (hit, miss, stale). Use this to spot miss spikes, stale reads, or unexpected lookup churn across tenant, JWKS, JWT, and S3 credential caches.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 120 + }, + "id": 226, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_cache_requests_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (cache, outcome)", + "legendFormat": "{{ cache }} ({{ outcome }})", + "range": true, + "refId": "A" + } + ], + "title": "Cache Lookup Rate by Outcome", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Derived cache hit ratio over time by cache. This treats misses and stale outcomes as non-hits so you can see whether each cache is actually avoiding backend lookups.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line+area" + } + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": 0 + }, + { + "color": "orange", + "value": 0.8 + }, + { + "color": "green", + "value": 0.95 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 120 + }, + "id": 230, + "options": { + "legend": { + "calcs": ["last", "min", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_cache_requests_total{region=~\"$region\", instance=~\"$instance\", outcome=\"hit\"}[$__rate_interval])) by (cache) / clamp_min(sum(rate(storage_api_otel_cache_requests_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (cache), 0.000001)", + "legendFormat": "{{ cache }}", + "range": true, + "refId": "A" + } + ], + "title": "Cache Hit Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Rate of LRU evictions by cache. Sustained eviction activity usually means the cache is undersized for the active tenant set or TTL/size bounds are causing churn.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 120 + }, + "id": 227, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_cache_evictions_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (cache)", + "legendFormat": "{{ cache }}", + "range": true, + "refId": "A" + } + ], + "title": "Cache Eviction Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Maximum live entries in a single monitored cache instance across the selected instances. Compare this directly against per-process item-count limits; use an ad hoc sum query instead when you want fleet-wide totals.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 128 + }, + "id": 228, + "options": { + "legend": { + "calcs": ["last", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "max(storage_api_otel_cache_entries{region=~\"$region\", instance=~\"$instance\"}) by (cache)", + "legendFormat": "{{ cache }}", + "range": true, + "refId": "A" + } + ], + "title": "Cache Entries (Max Instance)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Maximum estimated memory footprint of a single monitored cache instance across the selected instances. Compare this directly against per-process cache size limits; use an ad hoc sum query instead when you want fleet-wide totals.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 128 + }, + "id": 229, + "options": { + "legend": { + "calcs": ["last", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "max(storage_api_otel_cache_size_bytes{region=~\"$region\", instance=~\"$instance\"}) by (cache)", + "legendFormat": "{{ cache }}", + "range": true, + "refId": "A" + } + ], + "title": "Cache Size (Max Instance)", + "type": "timeseries" + } + ], + "title": "Cache", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 41 + }, + "id": 50, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Number of jobs currently pending in the queue by job type. Growing queues indicate processing bottlenecks. Monitor this to ensure background jobs are being processed in a timely manner.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 121 + }, + "id": 51, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_otel_queue_job_scheduled{region=~\"$region\", instance=~\"$instance\"}) by (name)", + "legendFormat": "{{ name }}", + "range": true, + "refId": "A" + } + ], + "title": "Queue Jobs Pending", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Number of successfully completed queue jobs by type. Use this to track job throughput and compare against scheduled jobs to understand completion rates.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 121 + }, + "id": 52, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_otel_queue_job_completed{region=~\"$region\", instance=~\"$instance\"}) by (name)", + "legendFormat": "{{ name }}", + "range": true, + "refId": "A" + } + ], + "title": "Queue Jobs Completed", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Queue job errors and retry failures by job type. Errors indicate jobs that failed and may be retried. Retry failures are jobs that exhausted all retry attempts. Investigate persistent failures for root cause.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "yellow", + "value": 1 + }, + { + "color": "red", + "value": 5 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 121 + }, + "id": 53, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_otel_queue_job_error{region=~\"$region\", instance=~\"$instance\"}) by (name)", + "legendFormat": "Errors {{ name }}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_otel_queue_job_retry_failed{region=~\"$region\", instance=~\"$instance\"}) by (name)", + "legendFormat": "Retries {{ name }}", + "refId": "B" + } + ], + "title": "Queue Errors & Retries", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Time taken to schedule jobs in the queue (p50, p95, p99). High scheduling times may indicate database contention, lock contention, or queue saturation. Use this to identify bottlenecks in job submission.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 129 + }, + "id": 224, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum(rate(storage_api_otel_queue_job_scheduled_time_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le, name))", + "legendFormat": "p50 {{ name }}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum(rate(storage_api_otel_queue_job_scheduled_time_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le, name))", + "legendFormat": "p95 {{ name }}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum(rate(storage_api_otel_queue_job_scheduled_time_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le, name))", + "legendFormat": "p99 {{ name }}", + "range": true, + "refId": "C" + } + ], + "title": "Queue Job Scheduling Time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Rate of jobs being scheduled vs completed per second. A growing gap between scheduled and completed indicates queue backlog. Use this to monitor queue throughput and identify processing bottlenecks.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "ops" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/Scheduled.*/" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/Completed.*/" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 129 + }, + "id": 225, + "options": { + "legend": { + "calcs": ["mean", "sum"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_queue_job_scheduled{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (name)", + "legendFormat": "Scheduled {{ name }}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_queue_job_completed{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (name)", + "legendFormat": "Completed {{ name }}", + "range": true, + "refId": "B" + } + ], + "title": "Scheduled vs Completed Rate", + "type": "timeseries" + } + ], + "title": "Queue", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 42 + }, + "id": 60, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "S3 multipart upload chunk latency percentiles (p50, p95, p99). High latency may indicate S3 throttling, network issues, or large chunk sizes. Compare across regions to identify geographic performance variations.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 233 + }, + "id": 61, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum(rate(storage_api_otel_s3_upload_part_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le))", + "legendFormat": "p50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "histogram_quantile(0.95, sum(rate(storage_api_otel_s3_upload_part_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le))", + "legendFormat": "p95", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "histogram_quantile(0.99, sum(rate(storage_api_otel_s3_upload_part_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le))", + "legendFormat": "p99", + "refId": "C" + } + ], + "title": "S3 Upload Part Latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "HTTP agent connection pool status showing busy and free sockets by protocol (http/https). Busy sockets indicate active connections. High busy-to-free ratios may indicate connection pool pressure or slow upstream services.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 233 + }, + "id": 62, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_otel_http_pool_busy_sockets{region=~\"$region\", instance=~\"$instance\"}) by (name, protocol)", + "legendFormat": "Busy {{ name }} ({{ protocol }})", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_otel_http_pool_free_sockets{region=~\"$region\", instance=~\"$instance\"}) by (name, protocol)", + "legendFormat": "Free {{ name }} ({{ protocol }})", + "refId": "B" + } + ], + "title": "HTTP Pool Sockets", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "HTTP agent pending requests and errors by pool. Pending requests queue when all connections are busy. Errors indicate connection failures to upstream services like S3 or external APIs.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 50 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 233 + }, + "id": 63, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_otel_http_pool_requests{region=~\"$region\", instance=~\"$instance\"}) by (name)", + "legendFormat": "Pending {{ name }}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_otel_http_pool_errors{region=~\"$region\", instance=~\"$instance\"}) by (name, type)", + "legendFormat": "Errors {{ name }} ({{ type }})", + "refId": "B" + } + ], + "title": "HTTP Pool Requests & Errors", + "type": "timeseries" + } + ], + "title": "S3 & HTTP Pool", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 43 + }, + "id": 80, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Node.js event loop utilization ratio (0-1). Values close to 1 indicate the event loop is fully utilized and may cause response delays. Sustained high utilization suggests CPU-bound work or blocking operations.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 139 + }, + "id": 81, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "storage_api_otel_nodejs_eventloop_utilization_ratio", + "legendFormat": "Event Loop Utilization", + "range": true, + "refId": "A" + } + ], + "title": "Event Loop Utilization", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Event loop delay percentiles measuring time between scheduled and actual callback execution. High p99 values indicate occasional blocking operations. Delays >100ms may cause noticeable request latency.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 139 + }, + "id": 82, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "storage_api_otel_nodejs_eventloop_delay_p50_seconds", + "legendFormat": "p50", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "storage_api_otel_nodejs_eventloop_delay_p90_seconds", + "legendFormat": "p90", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "storage_api_otel_nodejs_eventloop_delay_p99_seconds", + "legendFormat": "p99", + "refId": "C" + } + ], + "title": "Event Loop Delay (Percentiles)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Network I/O throughput and errors from host metrics. High throughput correlates with upload/download activity. Network errors may indicate connectivity issues, DNS failures, or upstream service problems.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "Bps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 139 + }, + "id": 73, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "rate(storage_api_otel_system_network_io_total[$__rate_interval])", + "legendFormat": "Network I/O", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "rate(storage_api_otel_system_network_errors_total[$__rate_interval])", + "legendFormat": "Network Errors", + "refId": "B" + } + ], + "title": "Network I/O", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "CPU utilization from host metrics: process CPU (this Node.js instance) vs system CPU (entire host). Compare to identify if the Storage API is the primary CPU consumer or if other processes are competing.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 147 + }, + "id": 71, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "storage_api_otel_process_cpu_utilization", + "legendFormat": "Process CPU Utilization", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "storage_api_otel_system_cpu_utilization", + "legendFormat": "System CPU Utilization", + "refId": "B" + } + ], + "title": "CPU Utilization", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Memory usage from host metrics: process memory (this Node.js instance) vs system memory (entire host). Monitor for memory pressure and ensure adequate headroom for garbage collection spikes.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 147 + }, + "id": 72, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "storage_api_otel_process_memory_usage", + "legendFormat": "Process Memory", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "storage_api_otel_system_memory_usage", + "legendFormat": "System Memory", + "refId": "B" + } + ], + "title": "Memory Usage", + "type": "timeseries" + } + ], + "title": "Node.js Runtime (Event Loop)", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 44 + }, + "id": 84, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "V8 JavaScript engine heap memory usage vs limit. Used heap approaching the limit may trigger more frequent garbage collection or out-of-memory errors. Monitor for memory leaks (steadily increasing used heap).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 140 + }, + "id": 85, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_otel_v8js_memory_heap_used_bytes)", + "legendFormat": "Heap Used", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_otel_v8js_memory_heap_limit_bytes)", + "legendFormat": "Heap Limit", + "range": true, + "refId": "B" + } + ], + "title": "V8 Heap Memory", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Garbage collection duration percentiles by GC type (minor/major/incremental). Long GC pauses can cause request latency spikes. Major GC taking >100ms may require heap size tuning or memory optimization.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 140 + }, + "id": 86, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum(rate(storage_api_otel_v8js_gc_duration_seconds_bucket[$__rate_interval])) by (le, v8js_gc_type))", + "legendFormat": "p50 {{ v8js_gc_type }}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum(rate(storage_api_otel_v8js_gc_duration_seconds_bucket[$__rate_interval])) by (le, v8js_gc_type))", + "legendFormat": "p99 {{ v8js_gc_type }}", + "range": true, + "refId": "B" + } + ], + "title": "GC Duration by Type", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Process memory breakdown: RSS (total resident memory), External (C++ objects bound to JS), ArrayBuffers (binary data buffers). High external/arraybuffer memory may indicate large file operations or buffer leaks.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 140 + }, + "id": 87, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "storage_api_otel_nodejs_memory_rss_bytes{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "RSS", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "storage_api_otel_nodejs_memory_external_bytes{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "External", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "storage_api_otel_nodejs_memory_array_buffers_bytes{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "Array Buffers", + "range": true, + "refId": "C" + } + ], + "title": "Process Memory (RSS/External)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Rate of garbage collection operations per second by GC type. Minor GC is fast and frequent (young generation), Major GC is slower (full heap). High major GC rates may indicate memory pressure.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 148 + }, + "id": 221, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_v8js_gc_duration_seconds_count[$__rate_interval])) by (v8js_gc_type)", + "legendFormat": "{{ v8js_gc_type }}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_otel_v8js_gc_duration_seconds_count[$__rate_interval]))", + "legendFormat": "Total", + "range": true, + "refId": "B" + } + ], + "title": "GC Rate by Type", + "type": "timeseries" + } + ], + "title": "Node.js Runtime (V8 Memory & GC)", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 45 + }, + "id": 88, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Rate of CPU time consumption split between user (application code) and system (kernel operations) time. High system CPU may indicate excessive I/O operations or syscall overhead.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 141 + }, + "id": 89, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "rate(storage_api_otel_process_cpu_user_seconds_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])", + "legendFormat": "User CPU", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "rate(storage_api_otel_process_cpu_system_seconds_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])", + "legendFormat": "System CPU", + "range": true, + "refId": "B" + } + ], + "title": "Process CPU Time (Rate)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Active libuv handles (timers, sockets, file descriptors) and pending async requests. High handle counts may indicate resource leaks. Sudden spikes correlate with increased load or connection storms.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 141 + }, + "id": 90, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "storage_api_otel_nodejs_active_handles_total{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "Active Handles", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "storage_api_otel_nodejs_active_requests_total{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "Active Requests", + "refId": "B" + } + ], + "title": "Active Handles & Requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Time spent in different event loop phases (idle, active, poll). High poll time indicates waiting for I/O. High active time indicates CPU-bound work. Useful for understanding event loop behavior.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 141 + }, + "id": 91, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "rate(storage_api_otel_nodejs_eventloop_time_seconds_total[$__rate_interval])", + "legendFormat": "{{ nodejs_eventloop_state }}", + "range": true, + "refId": "A" + } + ], + "title": "Event Loop Time by State", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Percentage of file descriptors in use (open/max). High values (>80%) may indicate FD exhaustion risk. Monitor for leaks - a steadily increasing value without corresponding traffic increase suggests FD leaks. Only available on Linux.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line+area" + } + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "orange", + "value": 0.7 + }, + { + "color": "red", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 149 + }, + "id": 222, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "storage_api_otel_process_open_fds{region=~\"$region\", instance=~\"$instance\"} / storage_api_otel_process_max_fds{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "FD Usage", + "range": true, + "refId": "A" + } + ], + "title": "Used File Descriptors", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Number of open file descriptors over time. Useful for tracking FD usage patterns and identifying potential leaks. Compare with max FD limit to assess headroom. Only available on Linux.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 149 + }, + "id": 223, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "storage_api_otel_process_open_fds{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "Open FDs", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "storage_api_otel_process_max_fds{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "Max FDs", + "range": true, + "refId": "B" + } + ], + "title": "Open File Descriptors", + "type": "timeseries" + } + ], + "title": "Node.js Runtime (Process)", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 46 + }, + "id": 70, + "panels": [], + "title": "Node.js Runtime (Host Metrics)", + "type": "row" + } + ], + "preload": false, + "refresh": "5s", + "schemaVersion": 42, + "tags": ["storage", "otel", "api"], + "templating": { + "list": [ + { + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "definition": "label_values(storage_api_otel_process_start_time_seconds,region)", + "includeAll": true, + "multi": true, + "name": "region", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(storage_api_otel_process_start_time_seconds,region)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 2, + "regex": "", + "sort": 1, + "type": "query" + }, + { + "allValue": ".*", + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "definition": "label_values(storage_api_otel_process_start_time_seconds,instance)", + "includeAll": true, + "multi": true, + "name": "instance", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(storage_api_otel_process_start_time_seconds,instance)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 2, + "regex": "", + "sort": 1, + "type": "query" + }, + { + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "definition": "label_values(storage_api_otel_http_request_duration_seconds_count,tenantId)", + "includeAll": true, + "multi": true, + "name": "tenant", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(storage_api_otel_http_request_duration_seconds_count,tenantId)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 2, + "regex": "", + "sort": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Storage API - OTel Metrics", + "uid": "storage-otel-metrics", + "version": 4 +} diff --git a/monitoring/grafana/dashboards/storage.json b/monitoring/grafana/dashboards/storage.json index 33845199d..13a10393a 100644 --- a/monitoring/grafana/dashboards/storage.json +++ b/monitoring/grafana/dashboards/storage.json @@ -4,54 +4,35 @@ { "builtIn": 1, "datasource": { - "type": "datasource", - "uid": "grafana" + "type": "grafana", + "uid": "-- Grafana --" }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, "type": "dashboard" } ] }, - "description": "Monitor metrics for node.js and express router status.", + "description": "Storage API OTel Metrics Dashboard with tenant and region support", "editable": true, "fiscalYearStartMonth": 0, - "gnetId": 12230, - "graphTooltip": 0, + "graphTooltip": 1, "id": 2, "links": [], - "liveNow": false, "panels": [ { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, + "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, - "id": 121, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "refId": "A" - } - ], - "title": "Overview & Targets", + "id": 1, + "panels": [], + "title": "Overview", "type": "row" }, { @@ -59,6 +40,7 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Application Performance Index score based on request latency. Score >= 90% is good (green), 70-90% needs attention (orange), < 70% is poor (red). Target and tolerated thresholds are configurable.", "fieldConfig": { "defaults": { "color": { @@ -75,12 +57,14 @@ "type": "special" } ], + "max": 100, + "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#C4162A", - "value": null + "value": 0 }, { "color": "rgba(237, 129, 40, 0.89)", @@ -92,29 +76,25 @@ } ] }, - "unit": "percent", - "unitScale": true + "unit": "percent" }, "overrides": [] }, "gridPos": { "h": 2, - "w": 6, + "w": 5, "x": 0, "y": 1 }, - "id": 51, - "links": [], - "maxDataPoints": 100, + "id": 200, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -122,21 +102,19 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "(\n ( \n sum(storage_api_http_request_duration_seconds_bucket{instance=~\"$instance\",le=\"$target\",status_code=~\"^2..$\"})\n + (\n sum(storage_api_http_request_duration_seconds_bucket{instance=~\"$instance\",le=\"$tolerated\",status_code=~\"^2..$\"})\n - sum(storage_api_http_request_duration_seconds_bucket{instance=~\"$instance\",le=\"$target\",status_code=~\"^2..$\"})\n ) / 2\n ) / sum(storage_api_http_request_duration_seconds_count{instance=~\"$instance\",status_code=~\"^2..$\"})\n) * 100", - "interval": "", + "expr": "((sum(storage_api_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\", le=\"0.3\",status_code=~\"2..\"}) + (sum(storage_api_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\", le=\"1.2\",status_code=~\"2..\"}) - sum(storage_api_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\", le=\"0.3\",status_code=~\"2..\"})) / 2) / sum(storage_api_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"2..\"})) * 100", "legendFormat": "score", "refId": "A" } ], - "title": "Apdex Score: target $target s, tolerated $tolerated s", + "title": "Apdex Score", "type": "stat" }, { @@ -144,6 +122,7 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Current queries per second (QPS). Shows the real-time request rate across all endpoints.", "fieldConfig": { "defaults": { "color": { @@ -165,7 +144,7 @@ "steps": [ { "color": "#299c46", - "value": null + "value": 0 }, { "color": "rgba(237, 129, 40, 0.89)", @@ -177,29 +156,25 @@ } ] }, - "unit": "short", - "unitScale": true + "unit": "short" }, "overrides": [] }, "gridPos": { "h": 2, - "w": 2, - "x": 6, + "w": 3, + "x": 5, "y": 1 }, - "id": 57, - "links": [], - "maxDataPoints": 100, + "id": 201, "options": { "colorMode": "value", "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { - "calcs": [ - "max" - ], + "calcs": ["max"], "fields": "", "values": false }, @@ -207,16 +182,14 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "sum(rate(storage_api_http_request_duration_seconds_count{instance=~\"$instance\",tenant_id=~\"$tenant_id\"}[$__interval]))", - "interval": "", + "expr": "sum(rate(storage_api_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", "legendFormat": "", "refId": "A" } @@ -229,6 +202,7 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Total number of HTTP requests processed during the selected time range.", "fieldConfig": { "defaults": { "color": { @@ -252,37 +226,29 @@ "steps": [ { "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 + "value": 0 } ] }, - "unit": "none", - "unitScale": true + "unit": "none" }, "overrides": [] }, "gridPos": { "h": 2, - "w": 4, + "w": 3, "x": 8, "y": 1 }, - "id": 53, - "links": [], - "maxDataPoints": 100, + "id": 202, "options": { "colorMode": "none", "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -290,20 +256,15 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(\n\t\tincrease(storage_api_http_request_duration_seconds_sum{instance=~\"$instance\",tenant_id=~\"$tenant_id\"}[$__range]) OR on() vector(0)\n\t)", - "instant": false, - "interval": "", + "expr": "sum(increase(storage_api_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\"}[$__range]))", "legendFormat": "", - "range": true, "refId": "A" } ], @@ -315,6 +276,7 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Process restart count during the selected time range. High restart counts may indicate crashes or OOM issues.", "fieldConfig": { "defaults": { "color": { @@ -335,38 +297,38 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "#299c46", + "value": 0 }, { - "color": "red", - "value": 80 + "color": "rgba(237, 129, 40, 0.89)", + "value": 1 + }, + { + "color": "#C4162A", + "value": 2 } ] }, - "unit": "bytes", - "unitScale": true + "unit": "none" }, "overrides": [] }, "gridPos": { "h": 2, - "w": 4, - "x": 12, + "w": 3, + "x": 11, "y": 1 }, - "id": 59, - "links": [], - "maxDataPoints": 100, + "id": 203, "options": { - "colorMode": "none", + "colorMode": "background", "graphMode": "none", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -374,21 +336,19 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "sum(storage_api_http_response_size_bytes_sum{instance=~\"$instance\"}) + sum(storage_api_http_request_size_bytes_sum{instance=~\"$instance\"})", - "interval": "", + "expr": "sum(changes(storage_api_process_start_time_seconds{region=~\"$region\", instance=~\"$instance\"}[$__range]))", "legendFormat": "", "refId": "A" } ], - "title": "Transferred", + "title": "Restarts", "type": "stat" }, { @@ -396,11 +356,11 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Server error rate (5xx) as a percentage of total requests. Values above 1% require investigation.", "fieldConfig": { "defaults": { "color": { - "fixedColor": "rgb(31, 120, 193)", - "mode": "fixed" + "mode": "thresholds" }, "mappings": [ { @@ -413,42 +373,44 @@ "type": "special" } ], + "max": 100, + "min": 0, "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "#299c46", + "value": 0 }, { - "color": "red", - "value": 80 + "color": "rgba(237, 129, 40, 0.89)", + "value": 5 + }, + { + "color": "#C4162A", + "value": 10 } ] }, - "unit": "bytes", - "unitScale": true + "unit": "percent" }, "overrides": [] }, "gridPos": { "h": 2, "w": 3, - "x": 16, + "x": 14, "y": 1 }, - "id": 70, - "links": [], - "maxDataPoints": 100, + "id": 204, "options": { - "colorMode": "none", + "colorMode": "background", "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -456,21 +418,19 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "storage_api_http", - "interval": "", - "legendFormat": "", + "expr": "(sum(storage_api_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"5..\"} OR on() vector(0)) / sum(storage_api_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\"}))*100", + "legendFormat": "error %", "refId": "A" } ], - "title": "Transfer Rate", + "title": "5xx Errors", "type": "stat" }, { @@ -478,6 +438,7 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Client error rate (4xx) as a percentage of total requests. High rates may indicate API misuse or client bugs.", "fieldConfig": { "defaults": { "color": { @@ -494,46 +455,44 @@ "type": "special" } ], + "max": 100, + "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "#299c46", - "value": null + "value": 0 }, { "color": "rgba(237, 129, 40, 0.89)", - "value": 1 + "value": 10 }, { "color": "#C4162A", - "value": 2 + "value": 20 } ] }, - "unit": "none", - "unitScale": true + "unit": "percent" }, "overrides": [] }, "gridPos": { "h": 2, "w": 3, - "x": 19, + "x": 17, "y": 1 }, - "id": 61, - "links": [], - "maxDataPoints": 100, + "id": 205, "options": { "colorMode": "background", - "graphMode": "none", + "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -541,21 +500,19 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "sum(changes(storage_api_process_start_time_seconds{instance=~\"$instance\"}[$__range]))", - "interval": "", - "legendFormat": "", + "expr": "(sum(storage_api_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"4..\"} OR on() vector(0)) / sum(storage_api_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\"}))*100", + "legendFormat": "error %", "refId": "A" } ], - "title": "Restarts", + "title": "4xx Errors", "type": "stat" }, { @@ -563,60 +520,51 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "File upload statistics: started, completed, multipart, and standard uploads during the selected time range.", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, + "decimals": 0, "mappings": [], - "max": 1, - "min": 0, + "noValue": "0", "thresholds": { - "mode": "percentage", + "mode": "absolute", "steps": [ { "color": "green", - "value": null - }, - { - "color": "orange", - "value": 75 - }, - { - "color": "red", - "value": 90 + "value": 0 } ] }, - "unit": "percentunit", - "unitScale": true + "unit": "none" }, "overrides": [] }, "gridPos": { - "h": 4, - "w": 2, - "x": 0, - "y": 3 + "h": 2, + "w": 4, + "x": 20, + "y": 1 }, - "id": 126, + "id": 206, "options": { - "minVizHeight": 75, - "minVizWidth": 75, + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", "orientation": "auto", + "percentChangeColorMode": "standard", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto", - "text": {} + "showPercentChange": false, + "textMode": "value_and_name", + "wideLayout": true }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { @@ -624,21 +572,45 @@ "uid": "local_prometheus" }, "editorMode": "code", - "expr": "avg(rate(storage_api_process_cpu_seconds_total{instance=~\"$instance\"}[$__interval]))", + "expr": "sum(increase(storage_api_upload_started_total{region=~\"$region\", instance=~\"$instance\"}[$__range]))", + "legendFormat": "Started", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(increase(storage_api_upload_success_total{region=~\"$region\", instance=~\"$instance\"}[$__range]))", + "legendFormat": "Completed", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(increase(storage_api_upload_started_total{region=~\"$region\", instance=~\"$instance\"}[$__range]))", "hide": false, "instant": false, + "legendFormat": "__auto", "range": true, - "refId": "B" + "refId": "C" } ], - "title": "CPU Usage", - "type": "gauge" + "title": "File Uploads", + "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, + "description": "Current CPU usage as a percentage. Values above 75% indicate high CPU load, above 90% is critical.", "fieldConfig": { "defaults": { "color": { @@ -652,7 +624,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "orange", @@ -664,35 +636,31 @@ } ] }, - "unit": "percentunit", - "unitScale": true + "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 4, - "w": 2, - "x": 2, + "w": 6, + "x": 0, "y": 3 }, - "id": 152, + "id": 207, "options": { "minVizHeight": 75, "minVizWidth": 75, "orientation": "auto", "reduceOptions": { - "calcs": [ - "max" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, "showThresholdLabels": false, "showThresholdMarkers": true, - "sizing": "auto", - "text": {} + "sizing": "auto" }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { @@ -700,17 +668,26 @@ "uid": "local_prometheus" }, "editorMode": "code", - "exemplar": true, - "expr": "sum(rate(storage_api_process_cpu_seconds_total{instance=~\"$instance\"}[$__range]))", + "expr": "avg(rate(storage_api_process_cpu_seconds_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", + "legendFormat": "Avg", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "max(rate(storage_api_process_cpu_seconds_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", "hide": false, "instant": false, - "interval": "", - "legendFormat": "Current", - "refId": "A" + "legendFormat": "Max", + "range": true, + "refId": "B" } ], - "title": "MAX CPU Usage", - "transformations": [], + "title": "CPU", "type": "gauge" }, { @@ -718,6 +695,7 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Current RAM usage as a percentage of 2GB limit. Monitor for memory pressure and potential OOM issues.", "fieldConfig": { "defaults": { "color": { @@ -731,43 +709,39 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", - "value": 80 + "value": 0.8 } ] }, - "unit": "percentunit", - "unitScale": true + "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 4, - "w": 2, - "x": 4, + "w": 6, + "x": 6, "y": 3 }, - "id": 128, + "id": 209, "options": { "minVizHeight": 75, "minVizWidth": 75, "orientation": "auto", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, "showThresholdLabels": false, "showThresholdMarkers": true, - "sizing": "auto", - "text": {} + "sizing": "auto" }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { @@ -775,15 +749,26 @@ "uid": "local_prometheus" }, "editorMode": "code", - "exemplar": true, - "expr": "avg((storage_api_process_resident_memory_bytes) / (2*1024*1024*1024))", - "interval": "", - "legendFormat": "", + "expr": "avg((storage_api_process_memory_usage{region=~\"$region\", instance=~\"$instance\"}) / (2*1024*1024*1024))", + "legendFormat": "Avg", "range": true, "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "max(storage_api_process_resident_memory_bytes{region=~\"$region\", instance=~\"$instance\"}) / (2*1024*1024*1024)", + "hide": false, + "instant": false, + "legendFormat": "Max", + "range": true, + "refId": "B" } ], - "title": "RAM Usage", + "title": "RAM", "type": "gauge" }, { @@ -791,6 +776,7 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Event loop utilization ratio (0-1). Values close to 1 indicate the event loop is saturated.", "fieldConfig": { "defaults": { "color": { @@ -800,55 +786,47 @@ "max": 1, "min": 0, "thresholds": { - "mode": "percentage", + "mode": "absolute", "steps": [ { "color": "green", - "value": null - }, - { - "color": "#EAB839", - "value": 60 + "value": 0 }, { "color": "orange", - "value": 70 + "value": 0.7 }, { "color": "red", - "value": 80 + "value": 0.9 } ] }, - "unit": "percentunit", - "unitScale": true + "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 4, - "w": 2, - "x": 6, + "w": 3, + "x": 12, "y": 3 }, - "id": 153, + "id": 212, "options": { "minVizHeight": 75, "minVizWidth": 75, "orientation": "auto", "reduceOptions": { - "calcs": [ - "max" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, "showThresholdLabels": false, "showThresholdMarkers": true, - "sizing": "auto", - "text": {} + "sizing": "auto" }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { @@ -856,15 +834,12 @@ "uid": "local_prometheus" }, "editorMode": "code", - "exemplar": true, - "expr": "max(storage_api_process_resident_memory_bytes{instance=~\"$instance\"}) / (2*1024*1024*1024)", - "interval": "", - "legendFormat": "", + "expr": "storage_api_nodejs_eventloop_utilization_ratio{region=~\"$region\", instance=~\"$instance\"}", "range": true, "refId": "A" } ], - "title": "Max RAM Usage", + "title": "Event Loop", "type": "gauge" }, { @@ -872,6 +847,7 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Response counts grouped by HTTP status code category (2xx success, 4xx client errors, 5xx server errors).", "fieldConfig": { "defaults": { "color": { @@ -884,33 +860,29 @@ "steps": [ { "color": "dark-blue", - "value": null + "value": 0 } ] }, - "unit": "none", - "unitScale": true + "unit": "none" }, "overrides": [] }, "gridPos": { "h": 4, - "w": 4, - "x": 8, + "w": 5, + "x": 15, "y": 3 }, - "id": 146, - "links": [], - "maxDataPoints": 100, + "id": 211, "options": { "colorMode": "background", "graphMode": "area", "justifyMode": "center", "orientation": "vertical", + "percentChangeColorMode": "standard", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -918,21 +890,15 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(storage_api_http_request_duration_seconds_sum{status_code=~\"^[4]..$\"}) or vector(0)", - "hide": false, - "instant": false, - "interval": "", - "legendFormat": "400", - "range": true, + "expr": "sum(storage_api_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"2..\"}) or vector(0)", + "legendFormat": "2xx", "refId": "A" }, { @@ -940,14 +906,8 @@ "type": "prometheus", "uid": "local_prometheus" }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(storage_api_http_request_duration_seconds_sum{status_code=~\"^[5]..$\"}) or vector(0)", - "hide": false, - "instant": false, - "interval": "", - "legendFormat": "500", - "range": true, + "expr": "sum(storage_api_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"4..\"}) or vector(0)", + "legendFormat": "4xx", "refId": "B" }, { @@ -955,19 +915,12 @@ "type": "prometheus", "uid": "local_prometheus" }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(storage_api_http_request_duration_seconds_sum{status_code=~\"^[2]..$\"}) or vector(0)", - "hide": false, - "instant": true, - "interval": "", - "legendFormat": "200", - "range": false, + "expr": "sum(storage_api_http_request_duration_seconds_count{region=~\"$region\", instance=~\"$instance\", status_code=~\"5..\"}) or vector(0)", + "legendFormat": "5xx", "refId": "C" } ], "title": "Responses By Code", - "transformations": [], "type": "stat" }, { @@ -975,62 +928,49 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Active database connection pools. High counts may indicate connection pool pressure.", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], + "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "#299c46", - "value": null + "color": "green", + "value": 0 }, { - "color": "rgba(237, 129, 40, 0.89)", - "value": 5 + "color": "orange", + "value": 50 }, { - "color": "#C4162A", - "value": 10 + "color": "red", + "value": 100 } ] }, - "unit": "percent", - "unitScale": true + "unit": "none" }, "overrides": [] }, "gridPos": { - "h": 2, - "w": 2, - "x": 12, + "h": 4, + "w": 3, + "x": 20, "y": 3 }, - "id": 63, - "links": [], - "maxDataPoints": 100, + "id": 213, "options": { - "colorMode": "background", + "colorMode": "value", "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -1038,21 +978,19 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "(\n\tsum(\n\t\tstorage_api_http_request_duration_seconds_count{instance=~\"$instance\",status_code=~\"^[5]..$\",tenant_id=~\"$tenant_id\"} OR on() vector(0)\n\t) / \n\tsum(\n\t\tstorage_api_http_request_duration_seconds_count{instance=~\"$instance\",tenant_id=~\"$tenant_id\"}\n\t)\n)*100", - "interval": "", - "legendFormat": "error %", + "expr": "sum(storage_api_db_pool{region=~\"$region\", instance=~\"$instance\"})", + "legendFormat": "", "refId": "A" } ], - "title": "500 Errors", + "title": "DB Pools", "type": "stat" }, { @@ -1060,64 +998,172 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Pending queue jobs across all job types. Growing queues indicate processing bottlenecks.", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, - "decimals": 0, "mappings": [], - "noValue": "0", "thresholds": { "mode": "absolute", "steps": [ { "color": "green", - "value": null + "value": 0 + }, + { + "color": "orange", + "value": 100 + }, + { + "color": "red", + "value": 500 } ] }, - "unit": "none", - "unitScale": true + "unit": "none" }, "overrides": [] }, "gridPos": { "h": 4, "w": 4, - "x": 14, - "y": 3 + "x": 0, + "y": 7 }, - "id": 117, + "id": 214, "options": { "colorMode": "value", - "graphMode": "none", - "justifyMode": "center", - "orientation": "auto", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, "showPercentChange": false, - "textMode": "value_and_name", + "textMode": "auto", "wideLayout": true }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "editorMode": "code", - "exemplar": true, - "expr": "sum(increase(storage_api_upload_started{tenant_id=~\"$tenant_id\"}[$__range]))", - "interval": "", - "legendFormat": "Started", - "range": true, + "expr": "sum(storage_api_queue_job_scheduled{region=~\"$region\", instance=~\"$instance\"})", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Queue Jobs", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 11 + }, + "id": 215, + "panels": [], + "title": "Requests Overview", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "HTTP requests per second broken down by status code category. Shows total requests along with 2xx (success), 4xx (client errors), and 5xx (server errors) rates. Use this to monitor overall API traffic and identify error spikes.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 12 + }, + "id": 2, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(rate(storage_api_http_requests_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", + "legendFormat": "Total", "refId": "A" }, { @@ -1125,13 +1171,8 @@ "type": "prometheus", "uid": "local_prometheus" }, - "editorMode": "code", - "exemplar": true, - "expr": "sum(increase(storage_api_upload_success{tenant_id=~\"$tenant_id\"}[$__range]))", - "hide": false, - "interval": "", - "legendFormat": "Completed", - "range": true, + "expr": "sum(rate(storage_api_http_requests_total{region=~\"$region\", instance=~\"$instance\", status_code=~\"2xx\"}[$__rate_interval]))", + "legendFormat": "2xx", "refId": "B" }, { @@ -1139,13 +1180,8 @@ "type": "prometheus", "uid": "local_prometheus" }, - "editorMode": "code", - "exemplar": true, - "expr": "sum(increase(storage_api_upload_success{tenant_id=~\"$tenant_id\", is_multipart=\"true\"}[$__range])) or vector(0)", - "hide": false, - "interval": "", - "legendFormat": "Multi Part", - "range": true, + "expr": "sum(rate(storage_api_http_requests_total{region=~\"$region\", instance=~\"$instance\", status_code=~\"4xx\"}[$__rate_interval]))", + "legendFormat": "4xx", "refId": "C" }, { @@ -1153,148 +1189,170 @@ "type": "prometheus", "uid": "local_prometheus" }, - "editorMode": "code", - "exemplar": true, - "expr": "sum(increase(storage_api_upload_success{tenant_id=~\"$tenant_id\", is_multipart=\"false\"}[$__range]))", - "hide": false, - "instant": false, - "interval": "", - "legendFormat": "Standard", + "expr": "sum(rate(storage_api_http_requests_total{region=~\"$region\", instance=~\"$instance\", status_code=~\"5xx\"}[$__rate_interval]))", + "legendFormat": "5xx", "refId": "D" } ], - "title": "File Uploads", - "type": "stat" + "title": "Request Rate", + "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, + "description": "HTTP request duration percentiles (p50, p90, p99). P50 shows median latency, p90 shows latency for 90% of requests, and p99 captures tail latency. High p99 values may indicate slow database queries, external service delays, or resource contention.", "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "palette-classic" }, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" } - ], + }, + "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "#299c46", - "value": null - }, - { - "color": "rgba(237, 129, 40, 0.89)", - "value": 5 - }, - { - "color": "#C4162A", - "value": 10 + "color": "green", + "value": 0 } ] }, - "unit": "percent", - "unitScale": true + "unit": "s" }, "overrides": [] }, "gridPos": { - "h": 2, - "w": 2, - "x": 12, - "y": 5 + "h": 8, + "w": 8, + "x": 8, + "y": 12 }, - "id": 156, - "links": [], - "maxDataPoints": 100, + "id": 3, "options": { - "colorMode": "background", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "editorMode": "code", - "exemplar": true, - "expr": "(\n\tsum(\n\t\tstorage_api_http_request_duration_seconds_count{instance=~\"$instance\",status_code=~\"^[4]..$\",tenant_id=~\"$tenant_id\"} OR on() vector(0)\n\t) / \n\tsum(\n\t\tstorage_api_http_request_duration_seconds_count{instance=~\"$instance\",tenant_id=~\"$tenant_id\"}\n\t)\n)*100", - "interval": "", - "legendFormat": "error %", - "range": true, + "expr": "histogram_quantile(0.50, sum(rate(storage_api_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le))", + "legendFormat": "p50", "refId": "A" - } - ], - "title": "400 Errors", - "type": "stat" - }, - { - "collapsed": false, - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 7 - }, - "id": 115, - "panels": [], - "targets": [ + }, { "datasource": { "type": "prometheus", - "uid": "PBFA97CFB590B2093" + "uid": "local_prometheus" }, - "refId": "A" + "expr": "histogram_quantile(0.90, sum(rate(storage_api_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le))", + "legendFormat": "p90", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "histogram_quantile(0.99, sum(rate(storage_api_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le))", + "legendFormat": "p99", + "refId": "C" } ], - "title": "Requests", - "type": "row" + "title": "Request Latency (Percentiles)", + "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, + "description": "Server error rate as a percentage of total requests. Values above 1% may indicate service issues requiring investigation. Common causes include database connection failures, S3 errors, or application exceptions.", "fieldConfig": { "defaults": { "color": { - "mode": "continuous-BlPu" + "mode": "palette-classic" }, "custom": { - "align": "auto", - "cellOptions": { - "type": "auto" + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false }, - "inspect": false + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "thresholds": { @@ -1302,151 +1360,193 @@ "steps": [ { "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 + "value": 0 } ] }, - "unit": "none", - "unitScale": true + "unit": "percentunit" }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "route 2" - }, - "properties": [ - { - "id": "custom.width", - "value": 246 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "route 1" - }, - "properties": [ - { - "id": "custom.width", - "value": 305 - } - ] - } - ] + "overrides": [] }, "gridPos": { - "h": 9, - "w": 12, - "x": 0, - "y": 8 + "h": 8, + "w": 8, + "x": 16, + "y": 12 }, - "id": 149, + "id": 4, "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true }, - "showHeader": true, - "sortBy": [ - { - "desc": true, - "displayName": "Value (range)" - } - ] + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "sum(increase(storage_api_http_request_duration_seconds_count{tenant_id=~\"$tenant_id\"}[$__range])) by (tenant_id, route, method, status_code)", - "interval": "", - "legendFormat": "tenant_id={{tenant_id}};route={{route}};method={{method}};status_code={{status_code}}", + "expr": "sum(rate(storage_api_http_requests_total{region=~\"$region\", instance=~\"$instance\", status_code=~\"5xx\"}[$__rate_interval])) / sum(rate(storage_api_http_requests_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", + "legendFormat": "Error Rate", "refId": "A" } ], - "title": "Requests by Path", - "transformations": [ - { - "id": "seriesToRows", - "options": {} + "title": "Error Rate (5xx)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Network transfer rates based on HTTP Content-Length headers. Transfer In shows data received from clients (uploads, POST bodies). Transfer Out shows data sent to clients (downloads, responses). Useful for monitoring bandwidth usage and identifying large file transfers.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "Bps" }, - { - "id": "extractFields", - "options": { - "replace": false, - "source": "Metric" + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Transfer In" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Transfer Out" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 20 + }, + "id": 216, + "options": { + "legend": { + "calcs": ["mean", "max", "sum"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ { - "id": "groupBy", - "options": { - "fields": { - "Value": { - "aggregations": [ - "range" - ], - "operation": "aggregate" - }, - "method": { - "aggregations": [], - "operation": "groupby" - }, - "route": { - "aggregations": [], - "operation": "groupby" - }, - "status_code": { - "aggregations": [], - "operation": "groupby" - }, - "tenant_id": { - "aggregations": [], - "operation": "groupby" - } - } - } + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_http_request_size_bytes_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", + "legendFormat": "Transfer In", + "range": true, + "refId": "A" }, { - "id": "filterByValue", - "options": { - "filters": [ - { - "config": { - "id": "equal", - "options": { - "value": "" - } - }, - "fieldName": "Value (range)" - } - ], - "match": "any", - "type": "exclude" - } + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_http_response_size_bytes_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", + "legendFormat": "Transfer Out", + "range": true, + "refId": "B" } ], - "type": "table" + "title": "Transfer In/Out", + "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, + "description": "Top API endpoints by request count. Shows which routes receive the most traffic. Use this to identify hot paths, optimize frequently accessed endpoints, and understand API usage patterns.", "fieldConfig": { "defaults": { "color": { @@ -1457,6 +1557,9 @@ "cellOptions": { "type": "auto" }, + "footer": { + "reducers": [] + }, "inspect": false }, "mappings": [], @@ -1465,163 +1568,89 @@ "steps": [ { "color": "green", - "value": null - }, + "value": 0 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "route" + }, + "properties": [ { - "color": "red", - "value": 80 + "id": "custom.width", + "value": 300 } ] }, - "unitScale": true - }, - "overrides": [] + { + "matcher": { + "id": "byName", + "options": "Value" + }, + "properties": [ + { + "id": "displayName", + "value": "Requests/sec" + }, + { + "id": "unit", + "value": "reqps" + } + ] + } + ] }, "gridPos": { - "h": 9, + "h": 8, "w": 12, "x": 12, - "y": 8 + "y": 20 }, - "id": 150, + "id": 217, "options": { "cellHeight": "sm", "footer": { "countRows": false, "fields": "", - "reducer": [ - "sum" - ], + "reducer": ["sum"], "show": false }, "showHeader": true, "sortBy": [ { "desc": true, - "displayName": "Total (lastNotNull)" + "displayName": "Value" } ] }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "editorMode": "code", - "exemplar": true, - "expr": "sum(storage_api_http_request_summary_seconds_count{status_code=~\"$error_codes\"}) by (status_code, route, method, tenant_id)", - "interval": "", - "legendFormat": "tenant_id={{tenant_id}};status_code={{status_code}};route={{route}}", - "range": true, + "expr": "topk(10, sum by (route) (rate(storage_api_http_requests_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])))", + "format": "table", + "instant": true, + "legendFormat": "__auto", "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "editorMode": "code", - "exemplar": true, - "expr": "sum(storage_api_http_request_duration_seconds_count{tenant_id=~\"$tenant_id\", status_code=~\"$error_codes\"}) by (status_code, route, method, tenant_id)", - "hide": false, - "interval": "", - "legendFormat": "tenant_id={{tenant_id}};status_code={{status_code}};route={{route}}", - "range": true, - "refId": "B" } ], - "title": "Errors by Path", + "title": "Requests by Path", "transformations": [ - { - "id": "reduce", - "options": { - "includeTimeField": false, - "labelsToFields": true, - "mode": "seriesToRows", - "reducers": [ - "lastNotNull" - ] - } - }, - { - "id": "seriesToColumns", - "options": { - "byField": "tenant_id" - } - }, { "id": "organize", "options": { "excludeByName": { - "Field": true, - "Last *": false, "Time": true }, - "includeByName": {}, - "indexByName": { - "Field": 0, - "Total": 5, - "method": 2, - "route": 3, - "status_code": 4, - "tenant_id": 1 - }, - "renameByName": { - "Last *": "" - } - } - }, - { - "id": "groupBy", - "options": { - "fields": { - "Last *": { - "aggregations": [], - "operation": "groupby" - }, - "Total": { - "aggregations": [ - "lastNotNull" - ], - "operation": "aggregate" - }, - "method": { - "aggregations": [], - "operation": "groupby" - }, - "route": { - "aggregations": [], - "operation": "groupby" - }, - "status_code": { - "aggregations": [], - "operation": "groupby" - }, - "tenant_id": { - "aggregations": [], - "operation": "groupby" - } - } - } - }, - { - "id": "filterByValue", - "options": { - "filters": [ - { - "config": { - "id": "equal", - "options": { - "value": 0 - } - }, - "fieldName": "Total (lastNotNull)" - } - ], - "match": "any", - "type": "exclude" + "indexByName": {}, + "renameByName": {} } } ], @@ -1632,6 +1661,7 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Error count over time broken down by status code. Shows 4xx client errors and 5xx server errors separately to help distinguish between client-side issues and server-side problems.", "fieldConfig": { "defaults": { "color": { @@ -1644,8 +1674,9 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 0, + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, @@ -1653,13 +1684,14 @@ "viz": false }, "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, - "showPoints": "auto", + "showPoints": "never", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -1670,58 +1702,89 @@ } }, "mappings": [], - "noValue": "0", "thresholds": { "mode": "absolute", "steps": [ { "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 + "value": 0 } ] }, - "unitScale": true + "unit": "short" }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "4xx Errors" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "5xx Errors" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] }, "gridPos": { "h": 8, "w": 12, "x": 0, - "y": 17 + "y": 28 }, - "id": 142, + "id": 218, "options": { "legend": { - "calcs": [], - "displayMode": "list", + "calcs": ["mean", "max", "sum"], + "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, - "pluginVersion": "8.3.3", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "editorMode": "code", - "exemplar": false, - "expr": "storage_api_http_request_duration_seconds_count{status_code=~\"$error_codes\"}", - "hide": false, - "instant": false, - "legendFormat": "{{status_code}}", - "range": true, - "refId": "B" + "expr": "sum(rate(storage_api_http_requests_total{region=~\"$region\", instance=~\"$instance\", status_code=~\"4xx\"}[$__rate_interval]))", + "legendFormat": "4xx Errors", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(rate(storage_api_http_requests_total{region=~\"$region\", instance=~\"$instance\", status_code=~\"5xx\"}[$__rate_interval]))", + "legendFormat": "5xx Errors", + "refId": "B" } ], "title": "Errors Over Time", @@ -1732,42 +1795,21 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Top API endpoints by error count. Shows which routes are generating the most errors. Use this to identify problematic endpoints that need attention or debugging.", "fieldConfig": { "defaults": { "color": { - "mode": "continuous-BlPu" + "mode": "thresholds" }, "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "hue", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" + "align": "auto", + "cellOptions": { + "type": "auto" }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" + "footer": { + "reducers": [] }, - "thresholdsStyle": { - "mode": "off" - } + "inspect": false }, "mappings": [], "thresholds": { @@ -1775,63 +1817,125 @@ "steps": [ { "color": "green", - "value": null + "value": 0 + }, + { + "color": "orange", + "value": 1 }, { "color": "red", - "value": 80 + "value": 10 } ] - }, - "unit": "none", - "unitScale": true + } }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "route" + }, + "properties": [ + { + "id": "custom.width", + "value": 300 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value" + }, + "properties": [ + { + "id": "displayName", + "value": "Errors/sec" + }, + { + "id": "unit", + "value": "short" + } + ] + } + ] }, "gridPos": { "h": 8, "w": 12, "x": 12, - "y": 17 + "y": 28 }, - "id": 147, + "id": 219, "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "right", - "showLegend": false + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": ["sum"], + "show": false }, - "tooltip": { - "mode": "single", - "sort": "none" - } + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Value" + } + ] }, + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "sum(increase(storage_api_http_request_duration_seconds_count{tenant_id=~\"$tenant_id\"}[$__interval]))", - "interval": "", - "legendFormat": "{{ tenant_id }} - {{route}} - {{method}} ", + "expr": "topk(10, sum by (route) (rate(storage_api_http_requests_total{region=~\"$region\", instance=~\"$instance\", status_code=~\"4xx|5xx\"}[$__rate_interval])))", + "format": "table", + "instant": true, + "legendFormat": "__auto", "refId": "A" } ], - "title": "Total Number of Requests Over Time", - "type": "timeseries" + "title": "Errors by Path", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": {} + } + } + ], + "type": "table" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 36 + }, + "id": 10, + "panels": [], + "title": "HTTP Requests by Tenant", + "type": "row" }, { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, + "description": "HTTP requests per second grouped by tenant ID. Use this to identify high-traffic tenants, detect unusual activity patterns, or investigate tenant-specific issues. Filter by tenant using the dropdown above.", "fieldConfig": { "defaults": { "color": { - "mode": "continuous-BlPu" + "mode": "palette-classic" }, "custom": { "axisBorderShow": false, @@ -1840,26 +1944,28 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "hue", + "fillOpacity": 10, + "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, - "showPoints": "auto", + "showPoints": "never", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", - "mode": "none" + "mode": "normal" }, "thresholdsStyle": { "mode": "off" @@ -1871,16 +1977,11 @@ "steps": [ { "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 + "value": 0 } ] }, - "unit": "s", - "unitScale": true + "unit": "reqps" }, "overrides": [] }, @@ -1888,51 +1989,35 @@ "h": 8, "w": 12, "x": 0, - "y": 25 + "y": 37 }, - "id": 134, + "id": 11, "options": { "legend": { - "calcs": [ - "min", - "max" - ], - "displayMode": "list", + "calcs": ["mean", "max"], + "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "histogram_quantile(0.5, sum(increase(storage_api_http_request_duration_seconds_bucket{tenant_id=~\"$tenant_id\"}[$__interval])) by (le, route, method, tenant_id))", - "hide": true, - "interval": "", - "legendFormat": "{{ tenant_id }} - {{route}} - {{method}} ", + "expr": "sum(rate(storage_api_http_requests_total{region=~\"$region\", instance=~\"$instance\", tenantId=~\"$tenant\"}[$__rate_interval])) by (tenantId)", + "legendFormat": "{{ tenantId }}", "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "exemplar": true, - "expr": "sum(rate(storage_api_http_request_duration_seconds_sum{tenant_id=~\"$tenant_id\"}[$__interval])) by (le, route, method, tenant_id) / sum(rate(storage_api_http_request_duration_seconds_count{tenant_id=~\"$tenant_id\"}[$__interval])) by (le, route, method, tenant_id)", - "hide": false, - "interval": "", - "legendFormat": "{{ tenant_id }} - {{route}} - {{method}} ", - "refId": "B" } ], - "title": "AVG Response Time", + "title": "Request Rate by Tenant", "type": "timeseries" }, { @@ -1940,6 +2025,7 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "95th percentile request latency per tenant. Helps identify tenants experiencing performance issues due to large files, complex queries, or resource-intensive operations. Compare against baseline to detect anomalies.", "fieldConfig": { "defaults": { "color": { @@ -1952,8 +2038,9 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 0, + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, @@ -1961,13 +2048,14 @@ "viz": false }, "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, - "showPoints": "auto", + "showPoints": "never", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -1983,16 +2071,11 @@ "steps": [ { "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 + "value": 0 } ] }, - "unit": "s", - "unitScale": true + "unit": "s" }, "overrides": [] }, @@ -2000,320 +2083,546 @@ "h": 8, "w": 12, "x": 12, - "y": 25 + "y": 37 }, - "id": 103, - "interval": "5s", + "id": 12, "options": { "legend": { - "calcs": [ - "min", - "max", - "mean" - ], - "displayMode": "list", + "calcs": ["mean", "max"], + "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "histogram_quantile(0.95, sum(increase(storage_api_http_request_duration_seconds_bucket{tenant_id=~\"$tenant_id\"}[$__interval])) by (le, route, method, status_code, tenant_id))", - "format": "time_series", - "instant": false, - "interval": "", - "intervalFactor": 1, - "legendFormat": "{{ tenant_id}} {{method}} {{route}}", + "expr": "histogram_quantile(0.95, sum(rate(storage_api_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\", tenantId=~\"$tenant\"}[$__rate_interval])) by (le, tenantId))", + "legendFormat": "{{ tenantId }}", "refId": "A" } ], - "title": "Http Request Duration (p95)", + "title": "P95 Latency by Tenant", "type": "timeseries" }, { - "collapsed": false, - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, + "collapsed": true, "gridPos": { "h": 1, "w": 24, "x": 0, - "y": 33 + "y": 45 }, - "id": 98, - "panels": [], - "targets": [ + "id": 20, + "panels": [ { "datasource": { "type": "prometheus", - "uid": "PBFA97CFB590B2093" + "uid": "local_prometheus" }, - "refId": "A" - } - ], - "title": "Queue", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" + "description": "The 10 most frequently called API endpoints by requests per second. Use this to understand API usage patterns, identify heavily-used endpoints for optimization, and detect unexpected traffic patterns.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] }, - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 46 }, - "unitScale": true - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 8, - "x": 0, - "y": 34 - }, - "id": 105, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "range" + "id": 21, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "topk(10, sum(rate(storage_api_http_requests_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (route))", + "legendFormat": "{{ route }}", + "refId": "A" + } ], - "fields": "", - "values": false + "title": "Top 10 Routes by Request Rate", + "type": "timeseries" }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.3.1", - "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "editorMode": "code", - "exemplar": true, - "expr": "sum(storage_api_queue_job_scheduled{tenant_id=~\"$tenant_id\"}) by (name)", - "interval": "", - "legendFormat": "{{ name }}", - "range": true, - "refId": "A" - } - ], - "title": "Queue Pending Messages", - "type": "stat" - }, - { + "description": "The 10 slowest API endpoints by 95th percentile latency. These endpoints are prime candidates for performance optimization. High latency may indicate complex database operations, large file transfers, or inefficient code paths.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 46 + }, + "id": 22, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "topk(10, histogram_quantile(0.95, sum(rate(storage_api_http_request_duration_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le, route)))", + "legendFormat": "{{ route }}", + "range": true, + "refId": "A" + } + ], + "title": "Top 10 Slowest Routes (P95)", + "type": "timeseries" + } + ], + "title": "HTTP Requests by Route", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 46 + }, + "id": 30, + "panels": [], + "title": "Uploads", + "type": "row" + }, + { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, + "description": "Comparison of started uploads versus successfully completed uploads. A growing gap between these values may indicate upload failures, timeouts, or client disconnections. Use this to monitor upload reliability.", "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, + "decimals": 0, "mappings": [], - "noValue": "0", "thresholds": { "mode": "absolute", "steps": [ { - "color": "green" - }, - { - "color": "red", - "value": 1 + "color": "green", + "value": 0 } ] }, - "unitScale": true + "unit": "short" }, "overrides": [] }, "gridPos": { "h": 8, - "w": 2, - "x": 8, - "y": 34 + "w": 8, + "x": 0, + "y": 47 }, - "id": 129, + "id": 31, "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "max" - ], - "fields": "", - "values": false + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "sum(storage_api_queue_job_retry_failed{tenant_id=~\"$tenant_id\"}) by (name)", - "interval": "", - "legendFormat": "{{ name }}", + "editorMode": "code", + "expr": "sum(storage_api_upload_started_total{region=~\"$region\", instance=~\"$instance\"})", + "legendFormat": "Started", + "range": true, "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_upload_success_total{region=~\"$region\", instance=~\"$instance\"})", + "legendFormat": "Success", + "range": true, + "refId": "B" } ], - "title": "Queue Failed and Retried", - "type": "gauge" + "title": "Uploads (Started vs Success)", + "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, + "description": "Successful uploads categorized by upload method: standard (single request), resumable (TUS protocol), S3 (direct S3 upload), and multipart. Helps understand which upload mechanisms are most used and their success rates.", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "mappings": [], - "noValue": "0", "thresholds": { "mode": "absolute", "steps": [ { - "color": "green" - }, - { - "color": "red", - "value": 1 + "color": "green", + "value": 0 } ] }, - "unitScale": true + "unit": "short" }, "overrides": [] }, "gridPos": { "h": 8, - "w": 3, - "x": 10, - "y": 34 + "w": 8, + "x": 8, + "y": 47 }, - "id": 107, + "id": 32, "options": { - "minVizHeight": 75, - "minVizWidth": 75, + "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", "orientation": "auto", "reduceOptions": { - "calcs": [ - "max" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "sum(storage_api_queue_job_error{tenant_id=~\"$tenant_id\"}) by (name)", - "interval": "", - "legendFormat": "{{ name }}", + "editorMode": "code", + "expr": "sum(storage_api_upload_success_total{region=~\"$region\", instance=~\"$instance\", is_standard=\"1\"})", + "legendFormat": "Standard", + "range": true, "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_upload_success_total{region=~\"$region\", instance=~\"$instance\", is_resumable=\"1\"})", + "legendFormat": "Resumable (TUS)", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_upload_success_total{region=~\"$region\", instance=~\"$instance\", is_s3=\"1\"})", + "legendFormat": "S3", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_upload_success_total{region=~\"$region\", instance=~\"$instance\", is_multipart=\"1\"})", + "legendFormat": "Multipart", + "range": true, + "refId": "D" } ], - "title": "Queue Job Failed", - "type": "gauge" + "title": "Uploads by Type", + "type": "bargauge" }, { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, + "description": "Successful uploads grouped by tenant ID. Use this to identify high-volume uploaders, track tenant activity, and investigate tenant-specific upload issues.", "fieldConfig": { "defaults": { "color": { - "mode": "continuous-BlPu" + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": 0 } ] }, - "unitScale": true + "unit": "short" }, "overrides": [] }, "gridPos": { "h": 8, - "w": 11, - "x": 13, - "y": 34 + "w": 8, + "x": 16, + "y": 47 }, - "id": 155, + "id": 33, "options": { - "displayMode": "gradient", - "maxVizHeight": 300, - "minVizHeight": 10, - "minVizWidth": 0, - "namePlacement": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true }, - "showUnfilled": true, - "sizing": "auto", - "valueMode": "color" + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } }, - "pluginVersion": "10.3.1", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { @@ -2321,115 +2630,1385 @@ "uid": "local_prometheus" }, "editorMode": "code", - "exemplar": false, - "expr": "sum(storage_api_queue_job_completed{tenant_id=~\"$tenant_id\", name=~\"^[a-z].*\"}) by (name)", - "instant": true, - "interval": "", - "legendFormat": "{{ name }}", - "range": false, + "expr": "sum(storage_api_upload_success_total{region=~\"$region\", instance=~\"$instance\", tenantId=~\"$tenant\"}) by (tenantId)", + "legendFormat": "{{ tenantId }}", + "range": true, + "refId": "A" + } + ], + "title": "Uploads by Tenant", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 55 + }, + "id": 40, + "panels": [], + "title": "Database", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Database query latency percentiles (p50, p95) grouped by operation type. Identifies slow queries that may need optimization through indexing, query rewriting, or connection pool tuning. Monitor for performance regressions.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 56 + }, + "id": 41, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "histogram_quantile(0.50, sum(rate(storage_api_database_query_performance_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le, name))", + "legendFormat": "p50 {{ name }}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "histogram_quantile(0.95, sum(rate(storage_api_database_query_performance_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le, name))", + "legendFormat": "p95 {{ name }}", + "refId": "B" + } + ], + "title": "Database Query Performance by Operation", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Active database connection pool count. High values may indicate connection leaks or pool exhaustion. Monitor this alongside query performance to ensure adequate connection availability.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 56 + }, + "id": 42, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_db_pool{region=~\"$region\", instance=~\"$instance\"})", + "legendFormat": "Active Pools", + "refId": "A" + } + ], + "title": "Database Pools", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Database connections categorized by type (internal vs external). External connections are from external services. Monitor for connection growth patterns and potential connection exhaustion.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 56 + }, + "id": 43, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_db_connections{region=~\"$region\", instance=~\"$instance\"}) by (is_external)", + "legendFormat": "External: {{ is_external }}", "refId": "A" } ], - "title": "Queue Completed", - "type": "bargauge" + "title": "Database Connections", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Rate of database queries per second grouped by operation type. Use this to monitor database load, identify query spikes, and correlate with application activity. High query rates may indicate opportunities for caching or query optimization.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 64 + }, + "id": 220, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(rate(storage_api_database_query_performance_count{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (name)", + "legendFormat": "{{ name }}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(rate(storage_api_database_query_performance_count{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval]))", + "legendFormat": "Total", + "refId": "B" + } + ], + "title": "Database Query Rate", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 72 + }, + "id": 50, + "panels": [], + "title": "Queue", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Number of jobs currently pending in the queue by job type. Growing queues indicate processing bottlenecks. Monitor this to ensure background jobs are being processed in a timely manner.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 73 + }, + "id": 51, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_queue_job_scheduled{region=~\"$region\", instance=~\"$instance\"}) by (name)", + "legendFormat": "{{ name }}", + "range": true, + "refId": "A" + } + ], + "title": "Queue Jobs Pending", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Number of successfully completed queue jobs by type. Use this to track job throughput and compare against scheduled jobs to understand completion rates.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 73 + }, + "id": 52, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(storage_api_queue_job_completed{region=~\"$region\", instance=~\"$instance\"}) by (name)", + "legendFormat": "{{ name }}", + "range": true, + "refId": "A" + } + ], + "title": "Queue Jobs Completed", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Queue job errors and retry failures by job type. Errors indicate jobs that failed and may be retried. Retry failures are jobs that exhausted all retry attempts. Investigate persistent failures for root cause.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "yellow", + "value": 1 + }, + { + "color": "red", + "value": 5 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 73 + }, + "id": 53, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_queue_job_error{region=~\"$region\", instance=~\"$instance\"}) by (name)", + "legendFormat": "Errors {{ name }}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_queue_job_retry_failed{region=~\"$region\", instance=~\"$instance\"}) by (name)", + "legendFormat": "Retries {{ name }}", + "refId": "B" + } + ], + "title": "Queue Errors & Retries", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Time taken to schedule jobs in the queue (p50, p95, p99). High scheduling times may indicate database contention, lock contention, or queue saturation. Use this to identify bottlenecks in job submission.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 81 + }, + "id": 224, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum(rate(storage_api_queue_job_scheduled_time_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le, name))", + "legendFormat": "p50 {{ name }}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum(rate(storage_api_queue_job_scheduled_time_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le, name))", + "legendFormat": "p95 {{ name }}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum(rate(storage_api_queue_job_scheduled_time_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le, name))", + "legendFormat": "p99 {{ name }}", + "range": true, + "refId": "C" + } + ], + "title": "Queue Job Scheduling Time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "Rate of jobs being scheduled vs completed per second. A growing gap between scheduled and completed indicates queue backlog. Use this to monitor queue throughput and identify processing bottlenecks.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "ops" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/Scheduled.*/" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/Completed.*/" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 81 + }, + "id": 225, + "options": { + "legend": { + "calcs": ["mean", "sum"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_queue_job_scheduled{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (name)", + "legendFormat": "Scheduled {{ name }}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(storage_api_queue_job_completed{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (name)", + "legendFormat": "Completed {{ name }}", + "range": true, + "refId": "B" + } + ], + "title": "Scheduled vs Completed Rate", + "type": "timeseries" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 89 + }, + "id": 60, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "S3 multipart upload chunk latency percentiles (p50, p95, p99). High latency may indicate S3 throttling, network issues, or large chunk sizes. Compare across regions to identify geographic performance variations.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 145 + }, + "id": 61, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum(rate(storage_api_s3_upload_part_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le))", + "legendFormat": "p50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "histogram_quantile(0.95, sum(rate(storage_api_s3_upload_part_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le))", + "legendFormat": "p95", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "histogram_quantile(0.99, sum(rate(storage_api_s3_upload_part_seconds_bucket{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])) by (le))", + "legendFormat": "p99", + "refId": "C" + } + ], + "title": "S3 Upload Part Latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "HTTP agent connection pool status showing busy and free sockets by protocol (http/https). Busy sockets indicate active connections. High busy-to-free ratios may indicate connection pool pressure or slow upstream services.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 145 + }, + "id": 62, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_http_pool_busy_sockets{region=~\"$region\", instance=~\"$instance\"}) by (name, protocol)", + "legendFormat": "Busy {{ name }} ({{ protocol }})", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_http_pool_free_sockets{region=~\"$region\", instance=~\"$instance\"}) by (name, protocol)", + "legendFormat": "Free {{ name }} ({{ protocol }})", + "refId": "B" + } + ], + "title": "HTTP Pool Sockets", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "description": "HTTP agent pending requests and errors by pool. Pending requests queue when all connections are busy. Errors indicate connection failures to upstream services like S3 or external APIs.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 50 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 145 + }, + "id": 63, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_http_pool_requests{region=~\"$region\", instance=~\"$instance\"}) by (name)", + "legendFormat": "Pending {{ name }}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "sum(storage_api_http_pool_errors{region=~\"$region\", instance=~\"$instance\"}) by (name, type)", + "legendFormat": "Errors {{ name }} ({{ type }})", + "refId": "B" + } + ], + "title": "HTTP Pool Requests & Errors", + "type": "timeseries" + } + ], + "title": "S3 & HTTP Pool", + "type": "row" }, { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - } - ] - }, - "unitScale": true - }, - "overrides": [] - }, + "collapsed": false, "gridPos": { - "h": 4, - "w": 8, + "h": 1, + "w": 24, "x": 0, - "y": 38 - }, - "id": 106, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "range" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "y": 90 }, - "pluginVersion": "10.3.1", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "editorMode": "code", - "exemplar": true, - "expr": "sum(storage_api_queue_job_completed{tenant_id=~\"$tenant_id\"}) by (name)", - "interval": "", - "legendFormat": "{{ name }}", - "range": true, - "refId": "A" - } - ], - "title": "Queue Completed Messages", - "type": "stat" + "id": 80, + "panels": [], + "title": "Node.js Runtime (Event Loop)", + "type": "row" }, { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, + "description": "Node.js event loop utilization ratio (0-1). Values close to 1 indicate the event loop is fully utilized and may cause response delays. Sustained high utilization suggests CPU-bound work or blocking operations.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 0, + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, - "lineInterpolation": "linear", - "lineWidth": 1, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, - "showPoints": "auto", + "showPoints": "never", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -2444,51 +4023,50 @@ "mode": "absolute", "steps": [ { - "color": "green" - }, - { - "color": "red", - "value": 80 + "color": "green", + "value": 0 } ] }, - "unit": "s" + "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 8, - "w": 12, + "w": 8, "x": 0, - "y": 42 + "y": 91 }, - "id": 137, + "id": 81, "options": { "legend": { - "calcs": [], - "displayMode": "list", + "calcs": ["mean", "max"], + "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "avg(rate(storage_api_queue_job_scheduled_time_sum{tenant_id=~\"$tenant_id\"}[$__interval]) / rate(storage_api_queue_job_scheduled_time_count{tenant_id=~\"$tenant_id\"}[$__interval])) by (le, name)", - "interval": "", - "legendFormat": "{{ name }}", + "editorMode": "code", + "expr": "storage_api_nodejs_eventloop_utilization_ratio", + "legendFormat": "Event Loop Utilization", + "range": true, "refId": "A" } ], - "title": "Queue Scheduling Time AVG", + "title": "Event Loop Utilization", "type": "timeseries" }, { @@ -2496,32 +4074,37 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Event loop delay percentiles measuring time between scheduled and actual callback execution. High p99 values indicate occasional blocking operations. Delays >100ms may cause noticeable request latency.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 0, + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, - "lineInterpolation": "linear", - "lineWidth": 1, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, - "showPoints": "auto", + "showPoints": "never", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -2536,113 +4119,104 @@ "mode": "absolute", "steps": [ { - "color": "green" - }, - { - "color": "red", - "value": 80 + "color": "green", + "value": 0 } ] }, - "unit": "none" + "unit": "s" }, "overrides": [] }, "gridPos": { "h": 8, - "w": 12, - "x": 12, - "y": 42 + "w": 8, + "x": 8, + "y": 91 }, - "id": 138, + "id": 82, "options": { "legend": { - "calcs": [], - "displayMode": "list", + "calcs": ["mean", "max"], + "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "sum(increase(storage_api_queue_job_scheduled_time_count[$__range])) by (name)", - "interval": "", - "legendFormat": "{{ name }}", + "expr": "storage_api_nodejs_eventloop_delay_p50_seconds", + "legendFormat": "p50", "refId": "A" - } - ], - "title": "Queue Messages Scheduled Over Time", - "type": "timeseries" - }, - { - "collapsed": false, - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 50 - }, - "id": 111, - "panels": [], - "targets": [ + }, { "datasource": { "type": "prometheus", - "uid": "PBFA97CFB590B2093" + "uid": "local_prometheus" }, - "refId": "A" + "expr": "storage_api_nodejs_eventloop_delay_p90_seconds", + "legendFormat": "p90", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "storage_api_nodejs_eventloop_delay_p99_seconds", + "legendFormat": "p99", + "refId": "C" } ], - "title": "Database", - "type": "row" + "title": "Event Loop Delay (Percentiles)", + "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, + "description": "Network I/O throughput and errors from host metrics. High throughput correlates with upload/download activity. Network errors may indicate connectivity issues, DNS failures, or upstream service problems.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 21, + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, - "lineInterpolation": "linear", - "lineStyle": { - "fill": "solid" - }, - "lineWidth": 1, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, - "showPoints": "auto", + "showPoints": "never", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -2657,54 +4231,44 @@ "mode": "absolute", "steps": [ { - "color": "green" - }, - { - "color": "red", - "value": 80 + "color": "green", + "value": 0 } ] }, - "unit": "s" + "unit": "Bps" }, "overrides": [] }, "gridPos": { "h": 8, - "w": 12, - "x": 0, - "y": 51 + "w": 8, + "x": 16, + "y": 91 }, - "id": 101, - "interval": "5s", + "id": 73, "options": { "legend": { - "calcs": [ - "min", - "max" - ], - "displayMode": "list", - "placement": "right", + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "histogram_quantile(0.95, sum(increase(storage_api_database_query_performance_bucket{tenant_id=~\"$tenant_id\"}[$__interval])) by (le, tenant_id, name))", - "format": "time_series", - "hide": false, - "instant": false, - "interval": "", - "legendFormat": "P95 - {{ tenant_id}} - {{name}}", + "expr": "rate(storage_api_system_network_io_total[$__rate_interval])", + "legendFormat": "Network I/O", "refId": "A" }, { @@ -2712,15 +4276,12 @@ "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "histogram_quantile(0.99, sum(increase(storage_api_database_query_performance_bucket{tenant_id=~\"$tenant_id\"}[$__interval])) by (le, tenant_id, name))", - "hide": false, - "interval": "", - "legendFormat": "P99 - {{ tenant_id}} - {{name}}", + "expr": "rate(storage_api_system_network_errors_total[$__rate_interval])", + "legendFormat": "Network Errors", "refId": "B" } ], - "title": "DB Query Performance (p95)", + "title": "Network I/O", "type": "timeseries" }, { @@ -2728,32 +4289,37 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "CPU utilization from host metrics: process CPU (this Node.js instance) vs system CPU (entire host). Compare to identify if the Storage API is the primary CPU consumer or if other processes are competing.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, - "drawStyle": "bars", - "fillOpacity": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, - "lineInterpolation": "linear", - "lineWidth": 1, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, - "showPoints": "auto", + "showPoints": "never", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -2768,190 +4334,95 @@ "mode": "absolute", "steps": [ { - "color": "green" - }, - { - "color": "red", - "value": 80 + "color": "green", + "value": 0 } ] }, - "unit": "s" + "unit": "percentunit" }, "overrides": [] }, "gridPos": { "h": 8, - "w": 12, - "x": 12, - "y": 51 + "w": 8, + "x": 0, + "y": 99 }, - "id": 123, + "id": 71, "options": { "legend": { - "calcs": [], - "displayMode": "list", + "calcs": ["mean", "max"], + "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "avg(rate(storage_api_database_query_performance_sum{tenant_id=~\"$tenant_id\"}[$__interval]) / rate(storage_api_database_query_performance_count{tenant_id=~\"$tenant_id\"}[$__interval])) by (le, name)", - "interval": "", - "legendFormat": "{{ name }}", + "expr": "storage_api_process_cpu_utilization", + "legendFormat": "Process CPU Utilization", "refId": "A" - } - ], - "title": "Database Performance AVG", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 59 - }, - "id": 102, - "interval": "5s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "sum(rate(storage_api_database_query_performance_count{tenant_id=~\"$tenant_id\"}[$__interval])) by (name)", - "instant": false, - "interval": "", - "legendFormat": "{{name}} ", - "refId": "A" + "expr": "storage_api_system_cpu_utilization", + "legendFormat": "System CPU Utilization", + "refId": "B" } ], - "title": "DB Query Rate", + "title": "CPU Utilization", "type": "timeseries" }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 67 - }, - "id": 157, - "panels": [], - "title": "PgBouncer - Pooler", - "type": "row" - }, { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, + "description": "Memory usage from host metrics: process memory (this Node.js instance) vs system memory (entire host). Monitor for memory pressure and ensure adequate headroom for garbage collection spikes.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 0, + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, - "lineInterpolation": "linear", - "lineWidth": 1, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, - "showPoints": "auto", + "showPoints": "never", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -2966,48 +4437,44 @@ "mode": "absolute", "steps": [ { - "color": "green" - }, - { - "color": "red", - "value": 80 + "color": "green", + "value": 0 } ] - } + }, + "unit": "bytes" }, "overrides": [] }, "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 68 + "h": 8, + "w": 8, + "x": 8, + "y": 99 }, - "id": 131, + "id": 72, "options": { "legend": { - "calcs": [], - "displayMode": "list", + "calcs": ["mean", "max"], + "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "editorMode": "code", - "exemplar": true, - "expr": "pgbouncer_databases_database_current_connections{database=\"postgres\"}", - "interval": "", - "legendFormat": "Native Connections", - "range": true, + "expr": "storage_api_process_memory_usage", + "legendFormat": "Process Memory", "refId": "A" }, { @@ -3015,65 +4482,61 @@ "type": "prometheus", "uid": "local_prometheus" }, - "editorMode": "code", - "exemplar": true, - "expr": "pgbouncer_pools_client_active_connections{database=\"postgres\"}", - "hide": false, - "interval": "", - "legendFormat": "Active Client Connections", - "range": true, + "expr": "storage_api_system_memory_usage", + "legendFormat": "System Memory", "refId": "B" } ], - "title": "Pg Connections", + "title": "Memory Usage", "type": "timeseries" }, { "collapsed": true, - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, "gridPos": { "h": 1, "w": 24, "x": 0, - "y": 76 + "y": 107 }, - "id": 87, + "id": 84, "panels": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, + "description": "V8 JavaScript engine heap memory usage vs limit. Used heap approaching the limit may trigger more frequent garbage collection or out-of-memory errors. Monitor for memory leaks (steadily increasing used heap).", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 2, + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, - "lineInterpolation": "linear", - "lineWidth": 1, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", - "spanNulls": true, + "showValues": false, + "spanNulls": false, "stacking": { "group": "A", "mode": "none" @@ -3082,58 +4545,51 @@ "mode": "off" } }, - "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green" - }, - { - "color": "red", - "value": 80 + "color": "green", + "value": 0 } ] }, - "unit": "s" + "unit": "bytes" }, "overrides": [] }, "gridPos": { - "h": 5, + "h": 8, "w": 8, "x": 0, - "y": 76 + "y": 147 }, - "id": 21, + "id": 85, "options": { "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "list", + "calcs": ["mean", "max"], + "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, - "pluginVersion": "8.3.3", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "storage_api_nodejs_eventloop_lag_seconds{instance=~\"$instance\"}", - "hide": false, - "instant": false, - "interval": "", - "legendFormat": "last", + "editorMode": "code", + "expr": "sum(storage_api_v8js_memory_heap_used_bytes)", + "legendFormat": "Heap Used", + "range": true, "refId": "A" }, { @@ -3141,27 +4597,14 @@ "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "storage_api_nodejs_eventloop_lag_p99_seconds{instance=~\"$instance\"}", - "hide": false, - "interval": "", - "legendFormat": "p99", + "editorMode": "code", + "expr": "sum(storage_api_v8js_memory_heap_limit_bytes)", + "legendFormat": "Heap Limit", + "range": true, "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "exemplar": true, - "expr": "storage_api_nodejs_eventloop_lag_p50_seconds{instance=~\"$instance\"}", - "hide": false, - "interval": "", - "legendFormat": "p50", - "refId": "C" } ], - "title": "Eventloop Latency", + "title": "V8 Heap Memory", "type": "timeseries" }, { @@ -3169,17 +4612,20 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Garbage collection duration percentiles by GC type (minor/major/incremental). Long GC pauses can cause request latency spikes. Major GC taking >100ms may require heap size tuning or memory optimization.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", @@ -3188,14 +4634,16 @@ "tooltip": false, "viz": false }, - "lineInterpolation": "linear", - "lineWidth": 1, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", - "spanNulls": true, + "showValues": false, + "spanNulls": false, "stacking": { "group": "A", "mode": "none" @@ -3204,251 +4652,51 @@ "mode": "off" } }, - "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green" - }, - { - "color": "red", - "value": 80 + "color": "green", + "value": 0 } ] }, - "unit": "percentunit" + "unit": "s" }, "overrides": [] }, "gridPos": { - "h": 5, + "h": 8, "w": 8, "x": 8, - "y": 76 - }, - "id": 42, - "options": { - "legend": { - "calcs": [ - "mean", - "max" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "exemplar": true, - "expr": "rate(storage_api_process_cpu_seconds_total{instance=~\"$instance\"}[$interval])", - "interval": "", - "legendFormat": "cpu", - "refId": "A" - } - ], - "title": "Rate of CPU Time Spent", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 2, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "links": [], - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 8, - "x": 16, - "y": 76 - }, - "id": 82, - "options": { - "legend": { - "calcs": [ - "lastNotNull", - "max" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "exemplar": true, - "expr": "storage_api_process_resident_memory_bytes{instance=~\"$instance\"} / (2*1024*1024*1024)", - "interval": "", - "legendFormat": "resident memory {{instance}}", - "refId": "A" - } - ], - "title": "Process Memory %", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 1, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "links": [], - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [] + "y": 147 }, - "gridPos": { - "h": 5, - "w": 8, - "x": 0, - "y": 81 - }, - "id": 124, + "id": 86, "options": { "legend": { - "calcs": [ - "lastNotNull", - "max" - ], - "displayMode": "list", + "calcs": ["mean", "max"], + "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, - "pluginVersion": "8.3.3", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "storage_api_process_virtual_memory_bytes{instance=~\"$instance\"}", - "interval": "", - "legendFormat": "virtual memory {{instance}}", + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum(rate(storage_api_v8js_gc_duration_seconds_bucket[$__rate_interval])) by (le, v8js_gc_type))", + "legendFormat": "p50 {{ v8js_gc_type }}", + "range": true, "refId": "A" }, { @@ -3456,14 +4704,14 @@ "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "storage_api_process_heap_bytes{instance=~\"$instance\"}", - "interval": "", - "legendFormat": "heap memory {{instance}}", + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum(rate(storage_api_v8js_gc_duration_seconds_bucket[$__rate_interval])) by (le, v8js_gc_type))", + "legendFormat": "p99 {{ v8js_gc_type }}", + "range": true, "refId": "B" } ], - "title": "Virtual Memory", + "title": "GC Duration by Type", "type": "timeseries" }, { @@ -3471,33 +4719,38 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Process memory breakdown: RSS (total resident memory), External (C++ objects bound to JS), ArrayBuffers (binary data buffers). High external/arraybuffer memory may indicate large file operations or buffer leaks.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 2, + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, - "lineInterpolation": "linear", - "lineWidth": 1, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", - "spanNulls": true, + "showValues": false, + "spanNulls": false, "stacking": { "group": "A", "mode": "none" @@ -3506,17 +4759,13 @@ "mode": "off" } }, - "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green" - }, - { - "color": "red", - "value": 80 + "color": "green", + "value": 0 } ] }, @@ -3525,272 +4774,101 @@ "overrides": [] }, "gridPos": { - "h": 5, + "h": 8, "w": 8, - "x": 8, - "y": 81 + "x": 16, + "y": 147 }, - "id": 127, + "id": 87, "options": { "legend": { - "calcs": [ - "lastNotNull", - "max" - ], - "displayMode": "list", + "calcs": ["mean", "max"], + "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "exemplar": true, - "expr": "storage_api_process_resident_memory_bytes{instance=~\"$instance\"}", - "interval": "", - "legendFormat": "resident memory {{instance}}", - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "exemplar": true, - "expr": "storage_api_process_heap_bytes{instance=~\"$instance\"}", - "interval": "", - "legendFormat": "heap memory {{instance}}", - "refId": "B" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } - ], - "title": "Process Memory", - "type": "timeseries" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "fieldConfig": { - "defaults": { - "links": [] - }, - "overrides": [] }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 5, - "w": 8, - "x": 0, - "y": 86 - }, - "hiddenSeries": false, - "id": 77, - "legend": { - "avg": false, - "current": true, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "10.0.1", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "storage_api_process_open_fds{instance=~\"$instance\"}", - "interval": "", - "legendFormat": "{{instance}}", + "editorMode": "code", + "expr": "storage_api_nodejs_memory_rss_bytes{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "RSS", + "range": true, "refId": "A" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Number of Open FDs", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "decimals": 0, - "format": "short", - "logBase": 1, - "show": true - }, - { - "format": "short", - "logBase": 1, - "show": false - } - ], - "yaxis": { - "align": false - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "fieldConfig": { - "defaults": { - "links": [] }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 5, - "w": 8, - "x": 8, - "y": 86 - }, - "hiddenSeries": false, - "id": 79, - "legend": { - "avg": false, - "current": true, - "max": false, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "10.0.1", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "( storage_api_process_open_fds{instance=~\"$instance\"} / storage_api_process_max_fds{instance=~\"$instance\"})", - "interval": "", - "legendFormat": "{{instance}}", - "refId": "A" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Used File Descriptors", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "percentunit", - "logBase": 1, - "show": true + "editorMode": "code", + "expr": "storage_api_nodejs_memory_external_bytes{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "External", + "range": true, + "refId": "B" }, { - "format": "short", - "logBase": 1, - "show": false + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "storage_api_nodejs_memory_array_buffers_bytes{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "Array Buffers", + "range": true, + "refId": "C" } ], - "yaxis": { - "align": false - } + "title": "Process Memory (RSS/External)", + "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, + "description": "Rate of garbage collection operations per second by GC type. Minor GC is fast and frequent (young generation), Major GC is slower (full heap). High major GC rates may indicate memory pressure.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 4, + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, - "lineInterpolation": "linear", - "lineWidth": 1, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", - "spanNulls": true, + "showValues": false, + "spanNulls": false, "stacking": { "group": "A", "mode": "none" @@ -3799,57 +4877,51 @@ "mode": "off" } }, - "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green" - }, - { - "color": "red", - "value": 80 + "color": "green", + "value": 0 } ] }, - "unit": "short" + "unit": "ops" }, "overrides": [] }, "gridPos": { - "h": 5, + "h": 8, "w": 8, - "x": 0, - "y": 91 + "x": 8, + "y": 155 }, - "id": 66, + "id": 221, "options": { "legend": { - "calcs": [ - "lastNotNull", - "max" - ], - "displayMode": "list", + "calcs": ["mean", "max"], + "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, - "pluginVersion": "8.3.3", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "storage_api_nodejs_active_handles_total{instance=~\"$instance\"}", - "interval": "", - "legendFormat": "Active Handler", + "editorMode": "code", + "expr": "sum(rate(storage_api_v8js_gc_duration_seconds_count[$__rate_interval])) by (v8js_gc_type)", + "legendFormat": "{{ v8js_gc_type }}", + "range": true, "refId": "A" }, { @@ -3857,168 +4929,67 @@ "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "storage_api_nodejs_active_requests_total{instance=~\"$instance\"}", - "interval": "", - "legendFormat": "Active Request", + "editorMode": "code", + "expr": "sum(rate(storage_api_v8js_gc_duration_seconds_count[$__rate_interval]))", + "legendFormat": "Total", + "range": true, "refId": "B" } ], - "title": "Active Handlers/Requests", + "title": "GC Rate by Type", "type": "timeseries" } ], - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "refId": "A" - } - ], - "title": "Node.js Process", + "title": "Node.js Runtime (V8 Memory & GC)", "type": "row" }, { "collapsed": true, - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, "gridPos": { "h": 1, "w": 24, "x": 0, - "y": 77 + "y": 108 }, - "id": 40, + "id": 88, "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "fieldConfig": { - "defaults": { - "links": [] - }, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 5, - "w": 12, - "x": 0, - "y": 77 - }, - "hiddenSeries": false, - "id": 43, - "legend": { - "avg": true, - "current": false, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "10.0.1", - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "exemplar": true, - "expr": "rate(storage_api_nodejs_gc_duration_seconds_sum{instance=~\"$instance\"}[$interval])", - "interval": "", - "legendFormat": "{{kind}}", - "refId": "A" - } - ], - "thresholds": [], - "timeRegions": [], - "title": "Rate of Garbage Collection Duration", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "s", - "label": "", - "logBase": 1, - "show": true - }, - { - "format": "short", - "logBase": 1, - "show": false - } - ], - "yaxis": { - "align": false - } - }, { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, + "description": "Rate of CPU time consumption split between user (application code) and system (kernel operations) time. High system CPU may indicate excessive I/O operations or syscall overhead.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 2, + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, - "lineInterpolation": "linear", - "lineWidth": 1, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", - "spanNulls": true, + "showValues": false, + "spanNulls": false, "stacking": { "group": "A", "mode": "none" @@ -4027,61 +4998,66 @@ "mode": "off" } }, - "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green" - }, - { - "color": "red", - "value": 80 + "color": "green", + "value": 0 } ] }, - "unit": "short" + "unit": "s" }, "overrides": [] }, "gridPos": { - "h": 5, - "w": 12, - "x": 12, - "y": 77 + "h": 8, + "w": 8, + "x": 0, + "y": 148 }, - "id": 38, + "id": 89, "options": { "legend": { - "calcs": [ - "mean", - "max" - ], - "displayMode": "list", + "calcs": ["mean", "max"], + "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, - "pluginVersion": "8.3.3", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "rate(storage_api_nodejs_gc_duration_seconds_count{instance=~\"$instance\"}[$interval])", - "interval": "", - "legendFormat": "{{kind}}", + "editorMode": "code", + "expr": "rate(storage_api_process_cpu_user_seconds_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])", + "legendFormat": "User CPU", + "range": true, "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "rate(storage_api_process_cpu_system_seconds_total{region=~\"$region\", instance=~\"$instance\"}[$__rate_interval])", + "legendFormat": "System CPU", + "range": true, + "refId": "B" } ], - "title": "Rate of Garbage Collection", + "title": "Process CPU Time (Rate)", "type": "timeseries" }, { @@ -4089,34 +5065,38 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Active libuv handles (timers, sockets, file descriptors) and pending async requests. High handle counts may indicate resource leaks. Sudden spikes correlate with increased load or connection storms.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 1, + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, - "lineInterpolation": "linear", - "lineWidth": 1, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { - "log": 2, - "type": "log" + "type": "linear" }, "showPoints": "never", - "spanNulls": true, + "showValues": false, + "spanNulls": false, "stacking": { "group": "A", "mode": "none" @@ -4125,60 +5105,62 @@ "mode": "off" } }, - "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green" - }, - { - "color": "red", - "value": 80 + "color": "green", + "value": 0 } ] }, - "unit": "s" + "unit": "short" }, "overrides": [] }, "gridPos": { - "h": 5, - "w": 12, - "x": 0, - "y": 82 + "h": 8, + "w": 8, + "x": 8, + "y": 148 }, - "id": 46, + "id": 90, "options": { "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "list", + "calcs": ["mean", "max"], + "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, - "pluginVersion": "8.3.3", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "storage_api_nodejs_gc_duration_seconds_sum{instance=~\"$instance\"}", - "interval": "", - "legendFormat": "{{kind}}", + "expr": "storage_api_nodejs_active_handles_total{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "Active Handles", "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "expr": "storage_api_nodejs_active_requests_total{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "Active Requests", + "refId": "B" } ], - "title": "Garbage Collection Duration", + "title": "Active Handles & Requests", "type": "timeseries" }, { @@ -4186,34 +5168,38 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Time spent in different event loop phases (idle, active, poll). High poll time indicates waiting for I/O. High active time indicates CPU-bound work. Useful for understanding event loop behavior.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 1, + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, - "lineInterpolation": "linear", - "lineWidth": 1, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { - "log": 2, - "type": "log" + "type": "linear" }, "showPoints": "never", - "spanNulls": true, + "showValues": false, + "spanNulls": false, "stacking": { "group": "A", "mode": "none" @@ -4222,60 +5208,55 @@ "mode": "off" } }, - "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green" - }, - { - "color": "red", - "value": 80 + "color": "green", + "value": 0 } ] }, - "unit": "short" + "unit": "s" }, "overrides": [] }, "gridPos": { - "h": 5, - "w": 12, - "x": 12, - "y": 82 + "h": 8, + "w": 8, + "x": 16, + "y": 148 }, - "id": 45, + "id": 91, "options": { "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "list", + "calcs": ["mean", "max"], + "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, - "pluginVersion": "8.3.3", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "storage_api_nodejs_gc_duration_seconds_count{instance=~\"$instance\"}", - "interval": "", - "legendFormat": "{{kind}}", + "editorMode": "code", + "expr": "rate(storage_api_nodejs_eventloop_time_seconds_total[$__rate_interval])", + "legendFormat": "{{ nodejs_eventloop_state }}", + "range": true, "refId": "A" } ], - "title": "Garbage Collection Count", + "title": "Event Loop Time by State", "type": "timeseries" }, { @@ -4283,122 +5264,104 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Percentage of file descriptors in use (open/max). High values (>80%) may indicate FD exhaustion risk. Monitor for leaks - a steadily increasing value without corresponding traffic increase suggests FD leaks. Only available on Linux.", "fieldConfig": { "defaults": { "color": { - "mode": "palette-classic" + "mode": "thresholds" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 5, + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, - "lineInterpolation": "linear", - "lineWidth": 1, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", - "spanNulls": true, + "showValues": false, + "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { - "mode": "off" + "mode": "line+area" } }, - "links": [], "mappings": [], + "max": 1, + "min": 0, "thresholds": { "mode": "absolute", "steps": [ { "color": "green" }, + { + "color": "orange", + "value": 0.7 + }, { "color": "red", - "value": 80 + "value": 0.9 } ] }, - "unit": "bytes" + "unit": "percentunit" }, "overrides": [] }, "gridPos": { - "h": 5, - "w": 12, + "h": 8, + "w": 8, "x": 0, - "y": 87 + "y": 164 }, - "id": 34, + "id": 222, "options": { "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "list", + "calcs": ["mean", "max"], + "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, - "pluginVersion": "8.3.3", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "storage_api_nodejs_heap_size_total_bytes{instance=~\"$instance\"}", - "interval": "", - "legendFormat": "total", + "editorMode": "code", + "expr": "storage_api_process_open_fds{region=~\"$region\", instance=~\"$instance\"} / storage_api_process_max_fds{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "FD Usage", + "range": true, "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "expr": "nodejs_heap_size_used_bytes{instance=~\"$instance\"}", - "legendFormat": "used", - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "expr": "process_resident_memory_bytes{instance=~\"$instance\"}", - "legendFormat": "resident", - "refId": "C" - }, - { - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "expr": "nodejs_external_memory_bytes{instance=~\"$instance\"}", - "legendFormat": "external", - "refId": "D" } ], - "title": "Heap Memory Usage", + "title": "Used File Descriptors", "type": "timeseries" }, { @@ -4406,380 +5369,204 @@ "type": "prometheus", "uid": "local_prometheus" }, + "description": "Number of open file descriptors over time. Useful for tracking FD usage patterns and identifying potential leaks. Compare with max FD limit to assess headroom. Only available on Linux.", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, + "barWidthFactor": 0.6, "drawStyle": "line", - "fillOpacity": 2, + "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, - "lineInterpolation": "linear", - "lineWidth": 1, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", - "spanNulls": true, + "showValues": false, + "spanNulls": false, "stacking": { "group": "A", - "mode": "normal" + "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, - "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { - "color": "green" - }, - { - "color": "red", - "value": 80 + "color": "green", + "value": 0 } ] }, - "unit": "bytes" + "unit": "short" }, "overrides": [] }, "gridPos": { - "h": 5, - "w": 12, - "x": 12, - "y": 87 + "h": 8, + "w": 8, + "x": 8, + "y": 164 }, - "id": 36, + "id": 223, "options": { "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "list", + "calcs": ["mean", "max"], + "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { - "mode": "single", - "sort": "none" + "hideZeros": false, + "mode": "multi", + "sort": "desc" } }, - "pluginVersion": "8.3.3", + "pluginVersion": "12.3.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "exemplar": true, - "expr": "storage_api_nodejs_heap_space_size_used_bytes{instance=~\"$instance\"}", - "interval": "", - "legendFormat": "{{space}}", + "editorMode": "code", + "expr": "storage_api_process_open_fds{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "Open FDs", + "range": true, "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "local_prometheus" + }, + "editorMode": "code", + "expr": "storage_api_process_max_fds{region=~\"$region\", instance=~\"$instance\"}", + "legendFormat": "Max FDs", + "range": true, + "refId": "B" } ], - "title": "Heap Space Used", + "title": "Open File Descriptors", "type": "timeseries" } ], - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "refId": "A" - } - ], - "title": "Node.js Garbage Collection", + "title": "Node.js Runtime (Process)", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 109 + }, + "id": 70, + "panels": [], + "title": "Node.js Runtime (Host Metrics)", "type": "row" } ], + "preload": false, "refresh": "5s", - "schemaVersion": 39, - "tags": [ - "node.js", - "express", - "prometheus" - ], + "schemaVersion": 42, + "tags": ["storage", "otel", "api"], "templating": { "list": [ { "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] + "text": ["local"], + "value": ["local"] }, "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "definition": "label_values(storage_api_nodejs_version_info, instance)", - "hide": 0, + "definition": "label_values(storage_api_process_start_time_seconds,region)", "includeAll": true, - "label": "instance", "multi": true, - "name": "instance", + "name": "region", "options": [], "query": { - "query": "label_values(storage_api_nodejs_version_info, instance)", - "refId": "StandardVariableQuery" + "qryType": 1, + "query": "label_values(storage_api_process_start_time_seconds,region)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" }, "refresh": 2, "regex": "", - "skipUrlSync": false, "sort": 1, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "auto": false, - "auto_count": 30, - "auto_min": "10s", - "current": { - "selected": false, - "text": "1m", - "value": "1m" - }, - "hide": 2, - "name": "interval", - "options": [ - { - "selected": true, - "text": "1m", - "value": "1m" - }, - { - "selected": false, - "text": "10m", - "value": "10m" - }, - { - "selected": false, - "text": "30m", - "value": "30m" - }, - { - "selected": false, - "text": "1h", - "value": "1h" - }, - { - "selected": false, - "text": "6h", - "value": "6h" - }, - { - "selected": false, - "text": "12h", - "value": "12h" - }, - { - "selected": false, - "text": "1d", - "value": "1d" - }, - { - "selected": false, - "text": "7d", - "value": "7d" - }, - { - "selected": false, - "text": "14d", - "value": "14d" - }, - { - "selected": false, - "text": "30d", - "value": "30d" - } - ], - "query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d", - "refresh": 2, - "skipUrlSync": false, - "type": "interval" - }, - { - "current": { - "isNone": true, - "selected": false, - "text": "None", - "value": "" - }, - "datasource": { - "type": "prometheus", - "uid": "local_prometheus" - }, - "definition": "label_values(storage_api_http_request_duration_seconds_bucket, le)", - "hide": 2, - "includeAll": false, - "multi": false, - "name": "target", - "options": [], - "query": { - "query": "label_values(storage_api_http_request_duration_seconds_bucket, le)", - "refId": "StandardVariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 3, "type": "query" }, { + "allValue": ".*", "current": { - "isNone": true, - "selected": false, - "text": "None", - "value": "" + "text": ["All"], + "value": ["$__all"] }, "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "definition": "label_values(storage_api_http_request_duration_seconds_bucket, le)", - "hide": 2, - "includeAll": false, - "multi": false, - "name": "tolerated", + "definition": "label_values(storage_api_process_start_time_seconds,instance)", + "includeAll": true, + "multi": true, + "name": "instance", "options": [], "query": { - "query": "label_values(storage_api_http_request_duration_seconds_bucket, le)", - "refId": "StandardVariableQuery" + "qryType": 1, + "query": "label_values(storage_api_process_start_time_seconds,instance)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" }, - "refresh": 1, + "refresh": 2, "regex": "", - "skipUrlSync": false, - "sort": 3, + "sort": 1, "type": "query" }, - { - "auto": false, - "auto_count": 30, - "auto_min": "10s", - "current": { - "selected": false, - "text": "1d", - "value": "1d" - }, - "hide": 2, - "name": "restarts_interval", - "options": [ - { - "selected": true, - "text": "1d", - "value": "1d" - }, - { - "selected": false, - "text": "7d", - "value": "7d" - }, - { - "selected": false, - "text": "14d", - "value": "14d" - }, - { - "selected": false, - "text": "30d", - "value": "30d" - } - ], - "query": "1d,7d,14d,30d", - "queryValue": "", - "refresh": 2, - "skipUrlSync": false, - "type": "interval" - }, { "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] + "text": ["storage-single-tenant"], + "value": ["storage-single-tenant"] }, "datasource": { "type": "prometheus", "uid": "local_prometheus" }, - "definition": "label_values(storage_api_http_request_duration_seconds_count, tenant_id)", - "hide": 0, + "definition": "label_values(storage_api_http_request_duration_seconds_count,tenantId)", "includeAll": true, "multi": true, - "name": "tenant_id", + "name": "tenant", "options": [], "query": { - "query": "label_values(storage_api_http_request_duration_seconds_count, tenant_id)", - "refId": "StandardVariableQuery" + "qryType": 1, + "query": "label_values(storage_api_http_request_duration_seconds_count,tenantId)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" }, "refresh": 2, "regex": "", - "skipUrlSync": false, - "sort": 0, + "sort": 1, "type": "query" - }, - { - "current": { - "selected": true, - "text": [ - "All" - ], - "value": [ - "$__all" - ] - }, - "hide": 0, - "includeAll": true, - "multi": true, - "name": "error_codes", - "options": [ - { - "selected": true, - "text": "All", - "value": "$__all" - }, - { - "selected": false, - "text": "5xx", - "value": "5xx" - }, - { - "selected": false, - "text": "4xx", - "value": "4xx" - } - ], - "query": "5xx,4xx", - "queryValue": "", - "skipUrlSync": false, - "type": "custom" } ] }, @@ -4787,23 +5574,9 @@ "from": "now-5m", "to": "now" }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ] - }, - "timezone": "", - "title": "Supabase Storage Dashboard", - "uid": "3J_78b6Zz", - "version": 1, - "weekStart": "" -} \ No newline at end of file + "timepicker": {}, + "timezone": "browser", + "title": "Storage API - Metrics", + "uid": "storage-metrics", + "version": 1 +} diff --git a/monitoring/grafana/dashboards/supavisor.json b/monitoring/grafana/dashboards/supavisor.json index 7391abd77..42f720934 100644 --- a/monitoring/grafana/dashboards/supavisor.json +++ b/monitoring/grafana/dashboards/supavisor.json @@ -90,9 +90,7 @@ "justifyMode": "auto", "orientation": "auto", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -153,9 +151,7 @@ "justifyMode": "auto", "orientation": "auto", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -216,9 +212,7 @@ "justifyMode": "auto", "orientation": "auto", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -2426,9 +2420,7 @@ "justifyMode": "auto", "orientation": "auto", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -2492,9 +2484,7 @@ "justifyMode": "auto", "orientation": "auto", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -2561,9 +2551,7 @@ "justifyMode": "auto", "orientation": "auto", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -2630,9 +2618,7 @@ "justifyMode": "auto", "orientation": "auto", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -2699,9 +2685,7 @@ "justifyMode": "auto", "orientation": "auto", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -2768,9 +2752,7 @@ "justifyMode": "auto", "orientation": "auto", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -3238,4 +3220,4 @@ "uid": "wyqSgGRSz", "version": 1, "weekStart": "" -} \ No newline at end of file +} diff --git a/monitoring/otel/config/otel-collector-config.yml b/monitoring/otel/config/otel-collector-config.yml index 0dd1e19e3..96ac4b293 100644 --- a/monitoring/otel/config/otel-collector-config.yml +++ b/monitoring/otel/config/otel-collector-config.yml @@ -9,164 +9,145 @@ processors: check_interval: 1s limit_percentage: 70 spike_limit_percentage: 20 - batch: + batch/traces: send_batch_size: 10000 timeout: 10s + batch/metrics: + send_batch_size: 10000 + timeout: 10s + # Copy region and instance from Resource attributes to metric data point attributes + # This ensures all metrics have the region and instance labels + transform/add_resource_attributes: + metric_statements: + - context: datapoint + statements: + - set(attributes["region"], resource.attributes["region"]) where resource.attributes["region"] != nil + - set(attributes["instance"], resource.attributes["instance"]) where resource.attributes["instance"] != nil + # Add storage_api_otel_ prefix to all metrics + # Note: storage_api_ transform must be FIRST to avoid double prefix + metricstransform/host: + transforms: + - include: ^storage_api_(.*)$$ + match_type: regexp + action: update + new_name: $${1} + # Catch-all: prefix any metric not already prefixed with storage_api_otel_ + - include: ^(.*)$$ + match_type: regexp + action: update + new_name: storage_api_otel_$${1} + + metricstransform/prom_prefix: + transforms: + - include: ^(.*)$$ + match_type: regexp + action: update + new_name: storage_api_$${1} + tail_sampling/storage: decision_wait: 10s expected_new_traces_per_sec: 10000 num_traces: 50000 - policies: - [ + policies: [ # Exclude probes URLs { name: exclude-urls, type: string_attribute, - string_attribute: { key: http.route, values: [ \/health.*, \/metrics, \/tenants, \/version, \/status ], enabled_regex_matching: true, invert_match: true } + string_attribute: + { + key: http.route, + values: [\/health.*, \/metrics, \/tenants, \/version, \/status], + enabled_regex_matching: true, + invert_match: true, + }, }, # All error are sampled { name: error-status-codes, type: numeric_attribute, - numeric_attribute: { key: http.status_code, min_value: 500, max_value: 599 } + numeric_attribute: { key: http.status_code, min_value: 500, max_value: 599 }, }, # Always sample high latency traces that are not uploads - { - name: high-latency-excluding-uploads, - type: and, - and: { - and_sub_policy: - [ - { - type: latency, - latency: { threshold_ms: 5000 } - }, - # Exclude upload operations - { - type: string_attribute, - string_attribute: { - key: http.operation, - values: [ .*upload.* ], - enabled_regex_matching: true, - invert_match: true - } - } - ] - } - }, + { name: high-latency-excluding-uploads, type: and, and: { and_sub_policy: [ + { type: latency, latency: { threshold_ms: 5000 } }, + # Exclude upload operations + { + type: string_attribute, + string_attribute: + { + key: http.operation, + values: [.*upload.*], + enabled_regex_matching: true, + invert_match: true, + }, + }, + ] } }, # Always sample high latency uploads - { - name: high-latency-uploads, - type: and, - and: { - and_sub_policy: - [ - { - type: latency, - latency: { threshold_ms: 300000 } - }, - # Only upload operations - { - type: string_attribute, - string_attribute: { - key: http.operation, - values: [ .*upload.* ], - enabled_regex_matching: true, - } - } - ] - } - }, + { name: high-latency-uploads, type: and, and: { and_sub_policy: [ + { type: latency, latency: { threshold_ms: 300000 } }, + # Only upload operations + { + type: string_attribute, + string_attribute: + { key: http.operation, values: [.*upload.*], enabled_regex_matching: true }, + }, + ] } }, # Sample traces for tenants with default mode # Default mode is the mode where the trace.mode attribute is set to basic # and only 0.2 of traces are sampled for each tenant - { - name: sampling-basic-tenants, - type: and, - and: { - and_sub_policy: - [ - { - # must have tenant.ref attribute - name: has-tenant-ref, - type: string_attribute, - string_attribute: - { - key: tenant.ref, - values: [ .* ], - enabled_regex_matching: true - }, - }, - { - # trace.mode = basic - name: trace-mode-default, - type: string_attribute, - string_attribute: - { - key: trace.mode, - values: [ basic ], - }, - }, - { - name: success-status-codes, - type: numeric_attribute, - numeric_attribute: { key: http.status_code, min_value: 200, max_value: 399 } - }, - { - name: basic-sampling, - type: probabilistic, - probabilistic: { - sampling_percentage: 5 - } - } - ] - } - }, + { name: sampling-basic-tenants, type: and, and: { and_sub_policy: [ + { + # must have tenant.ref attribute + name: has-tenant-ref, + type: string_attribute, + string_attribute: + { key: tenant.ref, values: [.*], enabled_regex_matching: true }, + }, + { + # trace.mode = basic + name: trace-mode-default, + type: string_attribute, + string_attribute: { key: trace.mode, values: [basic] }, + }, + { + name: success-status-codes, + type: numeric_attribute, + numeric_attribute: { key: http.status_code, min_value: 200, max_value: 399 }, + }, + { + name: basic-sampling, + type: probabilistic, + probabilistic: { sampling_percentage: 5 }, + }, + ] } }, # Sample traces for tenants with premium mode # Premium mode sample 100% of traces for each tenant - { - name: sampling-premium-tenants, - type: and, - and: { - and_sub_policy: - [ - { - # must have tenant.ref attribute - name: has-tenant-ref, - type: string_attribute, - string_attribute: - { - key: tenant.ref, - values: [ .* ], - enabled_regex_matching: true - }, - }, - { - # trace.mode = premium - name: trace-mode-default, - type: string_attribute, - string_attribute: - { - key: trace.mode, - values: [ full ], - }, - }, - { - name: success-status-codes, - type: numeric_attribute, - numeric_attribute: { key: http.status_code, min_value: 200, max_value: 399 } - }, - { - name: full-sampling, - type: probabilistic, - probabilistic: { - sampling_percentage: 100 - } - } - ] - } - } + { name: sampling-premium-tenants, type: and, and: { and_sub_policy: [ + { + # must have tenant.ref attribute + name: has-tenant-ref, + type: string_attribute, + string_attribute: + { key: tenant.ref, values: [.*], enabled_regex_matching: true }, + }, + { + # trace.mode = premium + name: trace-mode-default, + type: string_attribute, + string_attribute: { key: trace.mode, values: [full] }, + }, + { + name: success-status-codes, + type: numeric_attribute, + numeric_attribute: { key: http.status_code, min_value: 200, max_value: 400 }, + }, + { + name: full-sampling, + type: probabilistic, + probabilistic: { sampling_percentage: 100 }, + }, + ] } }, ] exporters: @@ -174,10 +155,29 @@ exporters: endpoint: "jaeger:4317" tls: insecure: true + prometheus: + endpoint: "0.0.0.0:9200" + prometheusremotewrite: + endpoint: "http://prometheus:9090/api/v1/write" service: pipelines: traces: receivers: [otlp] - processors: [memory_limiter, tail_sampling/storage, batch] - exporters: [otlp/jaeger] \ No newline at end of file + processors: [memory_limiter, tail_sampling/storage, batch/traces] + exporters: [otlp/jaeger] + metrics/otel: + receivers: [otlp] + processors: + [memory_limiter, transform/add_resource_attributes, metricstransform/host, batch/metrics] + exporters: [prometheusremotewrite] + metrics/prometheus: + receivers: [otlp] + processors: + [ + memory_limiter, + transform/add_resource_attributes, + metricstransform/prom_prefix, + batch/metrics, + ] + exporters: [prometheus] diff --git a/monitoring/prometheus/prometheus.yml b/monitoring/prometheus/prometheus.yml index a03a205b4..7349d7a5f 100644 --- a/monitoring/prometheus/prometheus.yml +++ b/monitoring/prometheus/prometheus.yml @@ -8,7 +8,7 @@ alerting: - targets: [] scheme: http timeout: 15s - api_version: v1 + api_version: v2 scrape_configs: - job_name: storage_api honor_timestamps: true @@ -18,11 +18,11 @@ scrape_configs: scheme: http static_configs: - targets: -# - storage:5000 -# - storage:5001 - - host.docker.internal:5001 + # - storage:5000 + # - storage:5001 + - otel:9200 - - job_name: 'postgres' + - job_name: "postgres" static_configs: - targets: - postgres_exporter:9187 @@ -47,4 +47,4 @@ scrape_configs: scheme: http static_configs: - targets: - - pg_bouncer_exporter:9127 \ No newline at end of file + - pg_bouncer_exporter:9127 diff --git a/package-lock.json b/package-lock.json index 1f8a93b72..31001b1e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,127 +7,113 @@ "": { "name": "supa-storage", "version": "1.11.2", + "hasInstallScript": true, "license": "ISC", "dependencies": { - "@aws-sdk/client-ecs": "^3.795.0", - "@aws-sdk/client-s3": "3.654.0", - "@aws-sdk/lib-storage": "3.654.0", - "@aws-sdk/s3-request-presigner": "3.654.0", - "@fastify/accepts": "^5.0.2", - "@fastify/multipart": "^9.2.1", + "@aws-sdk/client-ecs": "^3.1023.0", + "@aws-sdk/client-s3": "^3.1023.0", + "@aws-sdk/client-s3vectors": "^3.1023.0", + "@aws-sdk/lib-storage": "^3.1023.0", + "@aws-sdk/s3-request-presigner": "^3.1024.0", + "@fastify/accepts": "^5.0.4", + "@fastify/multipart": "^9.4.0", + "@fastify/otel": "^0.18.1", "@fastify/rate-limit": "^10.3.0", - "@fastify/swagger": "^9.5.1", - "@fastify/swagger-ui": "^5.2.3", + "@fastify/swagger": "^9.7.0", + "@fastify/swagger-ui": "^5.2.5", "@isaacs/ttlcache": "^1.4.1", - "@opentelemetry/api": "^1.8.0", - "@opentelemetry/auto-instrumentations-node": "^0.50.0", - "@opentelemetry/instrumentation-aws-sdk": "^0.44.0", - "@opentelemetry/instrumentation-fastify": "^0.39.0", - "@opentelemetry/instrumentation-http": "^0.53.0", - "@opentelemetry/instrumentation-knex": "^0.40.0", - "@opentelemetry/instrumentation-pg": "^0.44.0", + "@kubernetes/client-node": "^1.3.0", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/auto-instrumentations-node": "^0.70.1", + "@opentelemetry/exporter-metrics-otlp-grpc": "^0.215.0", + "@opentelemetry/exporter-prometheus": "^0.213.0", + "@opentelemetry/host-metrics": "^0.38.3", + "@opentelemetry/instrumentation-aws-sdk": "^0.59.0", + "@opentelemetry/instrumentation-http": "^0.213.0", + "@opentelemetry/instrumentation-knex": "^0.58.0", + "@opentelemetry/instrumentation-pg": "^0.64.0", + "@opentelemetry/instrumentation-runtime-node": "^0.25.0", + "@opentelemetry/sdk-metrics": "^2.6.1", + "@opentelemetry/sdk-node": "^0.213.0", + "@platformatic/control": "^3.52.0", + "@platformatic/flame": "^1.6.0", + "@platformatic/globals": "^3.52.0", + "@platformatic/node": "^3.52.0", + "@platformatic/wattpm-pprof-capture": "^3.52.1", "@shopify/semaphore": "^3.0.2", "@smithy/node-http-handler": "^2.3.1", "@tus/file-store": "2.0.0", - "@tus/s3-store": "2.0.1", + "@tus/s3-store": "2.0.2", "@tus/server": "2.2.1", "agentkeepalive": "^4.5.0", - "ajv": "^8.12.0", + "ajv": "^8.18.0", "async-retry": "^1.3.3", "aws-sigv4-sign": "^1.2.1", - "axios": "^1.9.0", + "axios": "^1.13.6", "axios-retry": "^3.9.1", - "connection-string": "^4.3.6", "conventional-changelog-conventionalcommits": "^5.0.0", - "crypto-js": "^4.2.0", "dotenv": "^16.0.0", - "fastify": "^5.6.0", - "fastify-metrics": "^12.1.0", - "fastify-plugin": "^5.0.1", - "fastify-xml-body-parser": "^2.2.0", - "fs-extra": "^10.0.1", + "fastify": "^5.8.3", + "fastify-plugin": "^5.1.0", "fs-xattr": "0.3.1", - "glob": "^11.0.0", "ioredis": "^5.2.4", - "ip-address": "^10.0.1", - "jose": "^6.0.10", + "ip-address": "^10.1.0", + "jose": "^6.2.2", + "json-bigint": "^1.0.0", "knex": "^3.1.0", - "lru-cache": "^10.2.0", - "md5-file": "^5.0.0", - "multistream": "^4.1.0", + "lru-cache": "^11.2.0", "object-sizeof": "^2.6.4", "pg": "^8.12.0", "pg-boss": "github:supabase/pg-boss#feat/exactly_once", "pg-listen": "^1.7.0", - "pino": "^9.7.0", + "pino": "^10.3.1", "pino-logflare": "^0.5.2", + "platformatic": "^3.52.0", "postgres-migrations": "^5.3.0", - "prom-client": "^15.1.3", + "pprof-format": "^2.2.1", + "wattpm": "^3.52.0", "xml2js": "^0.6.2" }, "bin": { "supa-storage": "dist/server.js" }, "devDependencies": { - "@aws-sdk/s3-presigned-post": "3.654.0", - "@babel/preset-env": "^7.26.9", - "@babel/preset-typescript": "^7.27.0", + "@aws-sdk/s3-presigned-post": "^3.1023.0", + "@biomejs/biome": "^2.4.4", "@types/async-retry": "^1.4.5", "@types/busboy": "^1.3.0", - "@types/crypto-js": "^4.1.1", - "@types/fs-extra": "^9.0.13", - "@types/glob": "^8.1.0", - "@types/jest": "^29.2.1", + "@types/cloneable-readable": "^2.0.3", "@types/js-yaml": "^4.0.5", - "@types/multistream": "^4.1.3", - "@types/mustache": "^4.2.2", - "@types/node": "^20.11.5", + "@types/json-bigint": "^1.0.4", + "@types/node": "^24.12.0", "@types/pg": "^8.6.4", "@types/stream-buffers": "^3.0.7", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.7.0", - "@typescript-eslint/parser": "^8.7.0", - "babel-jest": "^29.7.0", + "@vitest/coverage-v8": "^4.1.4", "esbuild": "^0.25.8", - "eslint": "^8.9.0", - "eslint-config-prettier": "^8.10.0", - "eslint-plugin-prettier": "^4.2.1", "form-data": "^4.0.0", - "jest": "^29.7.0", "js-yaml": "^4.1.0", "json-schema-to-ts": "^3.0.0", - "mustache": "^4.2.0", - "pino-pretty": "^8.1.0", + "patch-package": "^8.0.0", + "pino-pretty": "^13.1.3", "prettier": "^2.8.8", "resolve-tspaths": "^0.8.19", "stream-buffers": "^3.0.2", - "ts-jest": "^29.3.2", - "ts-node-dev": "^1.1.8", "tsx": "^4.16.0", "tus-js-client": "^3.1.0", - "typescript": "5.2.2" - }, - "engines": { - "node": ">= 20.0.0" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" + "typescript": "5.9.3", + "vitest": "^4.1.4" }, "engines": { - "node": ">=6.0.0" + "node": ">=24.0.0", + "npm": "11.12.1" } }, "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": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", @@ -141,6 +127,7 @@ "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", @@ -311,775 +298,743 @@ } }, "node_modules/@aws-sdk/client-ecs": { - "version": "3.795.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.795.0.tgz", - "integrity": "sha512-UQnd5Ft8jDBZW4JpkKceq49/2BDx3go/c8KNBQOaFK52DcS6k+6BSt0S5ZW9oP2w5MTvQJUnnbIa4PH9IRfXEA==", + "version": "3.1023.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.1023.0.tgz", + "integrity": "sha512-+EYwDGPS21xyatq1xTXlmmyKKkfk7lhAc+QlMznKnuM5mytM4ZzFAgp93NrgIemOqqervH5sbNDrKwurG8NAFA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.775.0", - "@aws-sdk/credential-provider-node": "3.787.0", - "@aws-sdk/middleware-host-header": "3.775.0", - "@aws-sdk/middleware-logger": "3.775.0", - "@aws-sdk/middleware-recursion-detection": "3.775.0", - "@aws-sdk/middleware-user-agent": "3.787.0", - "@aws-sdk/region-config-resolver": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@aws-sdk/util-endpoints": "3.787.0", - "@aws-sdk/util-user-agent-browser": "3.775.0", - "@aws-sdk/util-user-agent-node": "3.787.0", - "@smithy/config-resolver": "^4.1.0", - "@smithy/core": "^3.2.0", - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/hash-node": "^4.0.2", - "@smithy/invalid-dependency": "^4.0.2", - "@smithy/middleware-content-length": "^4.0.2", - "@smithy/middleware-endpoint": "^4.1.0", - "@smithy/middleware-retry": "^4.1.0", - "@smithy/middleware-serde": "^4.0.3", - "@smithy/middleware-stack": "^4.0.2", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/node-http-handler": "^4.0.4", - "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "@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.8", - "@smithy/util-defaults-mode-node": "^4.0.8", - "@smithy/util-endpoints": "^3.0.2", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-retry": "^4.0.2", - "@smithy/util-utf8": "^4.0.0", - "@smithy/util-waiter": "^4.0.3", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=18.0.0" + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/credential-provider-node": "^3.972.29", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.9", + "@aws-sdk/middleware-user-agent": "^3.972.28", + "@aws-sdk/region-config-resolver": "^3.972.10", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.14", + "@smithy/config-resolver": "^4.4.13", + "@smithy/core": "^3.23.13", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/middleware-retry": "^4.4.46", + "@smithy/middleware-serde": "^4.2.16", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.1", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.44", + "@smithy/util-defaults-mode-node": "^4.2.48", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.13", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.14", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/client-sso": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.787.0.tgz", - "integrity": "sha512-L8R+Mh258G0DC73ktpSVrG4TT9i2vmDLecARTDR/4q5sRivdDQSL5bUp3LKcK80Bx+FRw3UETIlX6mYMLL9PJQ==", + "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.775.0", - "@aws-sdk/middleware-host-header": "3.775.0", - "@aws-sdk/middleware-logger": "3.775.0", - "@aws-sdk/middleware-recursion-detection": "3.775.0", - "@aws-sdk/middleware-user-agent": "3.787.0", - "@aws-sdk/region-config-resolver": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@aws-sdk/util-endpoints": "3.787.0", - "@aws-sdk/util-user-agent-browser": "3.775.0", - "@aws-sdk/util-user-agent-node": "3.787.0", - "@smithy/config-resolver": "^4.1.0", - "@smithy/core": "^3.2.0", - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/hash-node": "^4.0.2", - "@smithy/invalid-dependency": "^4.0.2", - "@smithy/middleware-content-length": "^4.0.2", - "@smithy/middleware-endpoint": "^4.1.0", - "@smithy/middleware-retry": "^4.1.0", - "@smithy/middleware-serde": "^4.0.3", - "@smithy/middleware-stack": "^4.0.2", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/node-http-handler": "^4.0.4", - "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "@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.8", - "@smithy/util-defaults-mode-node": "^4.0.8", - "@smithy/util-endpoints": "^3.0.2", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-retry": "^4.0.2", - "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/core": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.775.0.tgz", - "integrity": "sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA==", + "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/node-http-handler": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.1.tgz", + "integrity": "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.775.0", - "@smithy/core": "^3.2.0", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/signature-v4": "^5.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/util-middleware": "^4.0.2", - "fast-xml-parser": "4.4.1", + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.775.0.tgz", - "integrity": "sha512-6ESVxwCbGm7WZ17kY1fjmxQud43vzJFoLd4bmlR+idQSWdqlzGDYdcfzpjDKTcivdtNrVYmFvcH1JBUwCRAZhw==", + "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@smithy/property-provider": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.775.0.tgz", - "integrity": "sha512-PjDQeDH/J1S0yWV32wCj2k5liRo0ssXMseCBEkCsD3SqsU8o5cU82b0hMX4sAib/RkglCSZqGO0xMiN0/7ndww==", + "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/node-http-handler": "^4.0.4", - "@smithy/property-provider": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/util-stream": "^4.2.0", + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.787.0.tgz", - "integrity": "sha512-hc2taRoDlXn2uuNuHWDJljVWYrp3r9JF1a/8XmOAZhVUNY+ImeeStylHXhXXKEA4JOjW+5PdJj0f1UDkVCHJiQ==", + "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.775.0", - "@aws-sdk/credential-provider-env": "3.775.0", - "@aws-sdk/credential-provider-http": "3.775.0", - "@aws-sdk/credential-provider-process": "3.775.0", - "@aws-sdk/credential-provider-sso": "3.787.0", - "@aws-sdk/credential-provider-web-identity": "3.787.0", - "@aws-sdk/nested-clients": "3.787.0", - "@aws-sdk/types": "3.775.0", - "@smithy/credential-provider-imds": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.787.0.tgz", - "integrity": "sha512-JioVi44B1vDMaK2CdzqimwvJD3uzvzbQhaEWXsGMBcMcNHajXAXf08EF50JG3ZhLrhhUsT1ObXpbTaPINOhh+g==", + "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.775.0", - "@aws-sdk/credential-provider-http": "3.775.0", - "@aws-sdk/credential-provider-ini": "3.787.0", - "@aws-sdk/credential-provider-process": "3.775.0", - "@aws-sdk/credential-provider-sso": "3.787.0", - "@aws-sdk/credential-provider-web-identity": "3.787.0", - "@aws-sdk/types": "3.775.0", - "@smithy/credential-provider-imds": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.775.0.tgz", - "integrity": "sha512-A6k68H9rQp+2+7P7SGO90Csw6nrUEm0Qfjpn9Etc4EboZhhCLs9b66umUsTsSBHus4FDIe5JQxfCUyt1wgNogg==", + "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.787.0.tgz", - "integrity": "sha512-fHc08bsvwm4+dEMEQKnQ7c1irEQmmxbgS+Fq41y09pPvPh31nAhoMcjBSTWAaPHvvsRbTYvmP4Mf12ZGr8/nfg==", + "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.787.0", - "@aws-sdk/core": "3.775.0", - "@aws-sdk/token-providers": "3.787.0", - "@aws-sdk/types": "3.775.0", - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.787.0.tgz", - "integrity": "sha512-SobmCwNbk6TfEsF283mZPQEI5vV2j6eY5tOCj8Er4Lzraxu9fBPADV+Bib2A8F6jlB1lMPJzOuDCbEasSt/RIw==", + "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.775.0", - "@aws-sdk/nested-clients": "3.787.0", - "@aws-sdk/types": "3.775.0", - "@smithy/property-provider": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.775.0.tgz", - "integrity": "sha512-tkSegM0Z6WMXpLB8oPys/d+umYIocvO298mGvcMCncpRl77L9XkvSLJIFzaHes+o7djAgIduYw8wKIMStFss2w==", + "node_modules/@aws-sdk/client-s3": { + "version": "3.1023.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1023.0.tgz", + "integrity": "sha512-IvNy49sdoCWd3fgHQxail3y0UQdfKj1Xk0VPu9HTwlog60o9Lmp5ykjZ2LlIuHEPaxq4Siih707GB/ulUWgetw==", + "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.973.26", + "@aws-sdk/credential-provider-node": "^3.972.29", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.8", + "@aws-sdk/middleware-expect-continue": "^3.972.8", + "@aws-sdk/middleware-flexible-checksums": "^3.974.6", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-location-constraint": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.9", + "@aws-sdk/middleware-sdk-s3": "^3.972.27", + "@aws-sdk/middleware-ssec": "^3.972.8", + "@aws-sdk/middleware-user-agent": "^3.972.28", + "@aws-sdk/region-config-resolver": "^3.972.10", + "@aws-sdk/signature-v4-multi-region": "^3.996.15", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.14", + "@smithy/config-resolver": "^4.4.13", + "@smithy/core": "^3.23.13", + "@smithy/eventstream-serde-browser": "^4.2.12", + "@smithy/eventstream-serde-config-resolver": "^4.3.12", + "@smithy/eventstream-serde-node": "^4.2.12", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-blob-browser": "^4.2.13", + "@smithy/hash-node": "^4.2.12", + "@smithy/hash-stream-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/md5-js": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/middleware-retry": "^4.4.46", + "@smithy/middleware-serde": "^4.2.16", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.1", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.44", + "@smithy/util-defaults-mode-node": "^4.2.48", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.13", + "@smithy/util-stream": "^4.5.21", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.14", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.775.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/middleware-logger": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.775.0.tgz", - "integrity": "sha512-FaxO1xom4MAoUJsldmR92nT1G6uZxTdNYOFYtdHfd6N2wcNaTuxgjIvqzg5y7QIH9kn58XX/dzf1iTjgqUStZw==", + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/node-http-handler": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.1.tgz", + "integrity": "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.775.0", - "@smithy/types": "^4.2.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.775.0.tgz", - "integrity": "sha512-GLCzC8D0A0YDG5u3F5U03Vb9j5tcOEFhr8oc6PDk0k0vm5VwtZOE6LvK7hcCSoAB4HXyOUM0sQuXrbaAh9OwXA==", + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.775.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.787.0.tgz", - "integrity": "sha512-Lnfj8SmPLYtrDFthNIaNj66zZsBCam+E4XiUDr55DIHTGstH6qZ/q6vg0GfbukxwSmUcGMwSR4Qbn8rb8yd77g==", + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@aws-sdk/util-endpoints": "3.787.0", - "@smithy/core": "^3.2.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.775.0.tgz", - "integrity": "sha512-40iH3LJjrQS3LKUJAl7Wj0bln7RFPEvUYKFxtP8a+oKFDO0F65F52xZxIJbPn6sHkxWDAnZlGgdjZXM3p2g5wQ==", + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.775.0", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/token-providers": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.787.0.tgz", - "integrity": "sha512-d7/NIqxq308Zg0RPMNrmn0QvzniL4Hx8Qdwzr6YZWLYAbUSvZYS2ppLR3BFWSkV6SsTJUx8BuDaj3P8vttkrog==", + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/nested-clients": "3.787.0", - "@aws-sdk/types": "3.775.0", - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/types": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.775.0.tgz", - "integrity": "sha512-ZoGKwa4C9fC9Av6bdfqcW6Ix5ot05F/S4VxWR2nHuMv7hzfmAjTOcUiWT7UR4hM/U0whf84VhDtXN/DWAk52KA==", + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/util-endpoints": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.787.0.tgz", - "integrity": "sha512-fd3zkiOkwnbdbN0Xp9TsP5SWrmv0SpT70YEdbb8wAj2DWQwiCmFszaSs+YCvhoCdmlR3Wl9Spu0pGpSAGKeYvQ==", + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.775.0", - "@smithy/types": "^4.2.0", - "@smithy/util-endpoints": "^3.0.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.775.0.tgz", - "integrity": "sha512-txw2wkiJmZKVdDbscK7VBK+u+TJnRtlUjRTLei+elZg2ADhpQxfVAQl436FUeIv6AhB/oRHW6/K/EAGXUSWi0A==", + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.775.0", - "@smithy/types": "^4.2.0", - "bowser": "^2.11.0", + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.787.0.tgz", - "integrity": "sha512-mG7Lz8ydfG4SF9e8WSXiPQ/Lsn3n8A5B5jtPROidafi06I3ckV2WxyMLdwG14m919NoS6IOfWHyRGSqWIwbVKA==", + "node_modules/@aws-sdk/client-s3vectors": { + "version": "3.1023.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3vectors/-/client-s3vectors-3.1023.0.tgz", + "integrity": "sha512-lOlRAWrfhK+STDdj6goo+Ysg4L5N+KPjeBEO57JeQaCysEQt/aSy0spIOpGaE8Nbd9sIo/bflZataKfKgnigzQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/credential-provider-node": "^3.972.29", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.9", + "@aws-sdk/middleware-user-agent": "^3.972.28", + "@aws-sdk/region-config-resolver": "^3.972.10", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.14", + "@smithy/config-resolver": "^4.4.13", + "@smithy/core": "^3.23.13", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/middleware-retry": "^4.4.46", + "@smithy/middleware-serde": "^4.2.16", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.1", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.44", + "@smithy/util-defaults-mode-node": "^4.2.48", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.13", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-s3vectors/node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.787.0", - "@aws-sdk/types": "3.775.0", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/abort-controller": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.2.tgz", - "integrity": "sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==", + "node_modules/@aws-sdk/client-s3vectors/node_modules/@smithy/node-http-handler": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.1.tgz", + "integrity": "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/config-resolver": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.0.tgz", - "integrity": "sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A==", + "node_modules/@aws-sdk/client-s3vectors/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/core": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.2.0.tgz", - "integrity": "sha512-k17bgQhVZ7YmUvA8at4af1TDpl0NDMBuBKJl8Yg0nrefwmValU+CnA5l/AriVdQNthU/33H3nK71HrLgqOPr1Q==", + "node_modules/@aws-sdk/client-s3vectors/node_modules/@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.0.3", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-stream": "^4.2.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/credential-provider-imds": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.2.tgz", - "integrity": "sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w==", + "node_modules/@aws-sdk/client-s3vectors/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/fetch-http-handler": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.2.tgz", - "integrity": "sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==", + "node_modules/@aws-sdk/client-s3vectors/node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/util-base64": "^4.0.0", + "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/hash-node": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.2.tgz", - "integrity": "sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==", + "node_modules/@aws-sdk/client-s3vectors/node_modules/@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/invalid-dependency": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.2.tgz", - "integrity": "sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==", + "node_modules/@aws-sdk/client-s3vectors/node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/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==", + "node_modules/@aws-sdk/client-s3vectors/node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", "license": "Apache-2.0", "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/middleware-content-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.2.tgz", - "integrity": "sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==", + "node_modules/@aws-sdk/core": { + "version": "3.973.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.26.tgz", + "integrity": "sha512-A/E6n2W42ruU+sfWk+mMUOyVXbsSgGrY3MJ9/0Az5qUdG67y8I6HYzzoAa+e/lzxxl1uCYmEL6BTMi9ZiZnplQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/xml-builder": "^3.972.16", + "@smithy/core": "^3.23.13", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/signature-v4": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/middleware-endpoint": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.0.tgz", - "integrity": "sha512-xhLimgNCbCzsUppRTGXWkZywksuTThxaIB0HwbpsVLY5sceac4e1TZ/WKYqufQLaUy+gUSJGNdwD2jo3cXL0iA==", + "node_modules/@aws-sdk/core/node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.2.0", - "@smithy/middleware-serde": "^4.0.3", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "@smithy/util-middleware": "^4.0.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/middleware-retry": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.0.tgz", - "integrity": "sha512-2zAagd1s6hAaI/ap6SXi5T3dDwBOczOMCSkkYzktqN1+tzbk1GAsHNAdo/1uzxz3Ky02jvZQwbi/vmDA6z4Oyg==", + "node_modules/@aws-sdk/core/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/service-error-classification": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-retry": "^4.0.2", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/middleware-serde": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.3.tgz", - "integrity": "sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==", + "node_modules/@aws-sdk/core/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/middleware-stack": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.2.tgz", - "integrity": "sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==", + "node_modules/@aws-sdk/core/node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/node-config-provider": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.2.tgz", - "integrity": "sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw==", + "node_modules/@aws-sdk/core/node_modules/@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/node-http-handler": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.4.tgz", - "integrity": "sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==", + "node_modules/@aws-sdk/core/node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/property-provider": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.2.tgz", - "integrity": "sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==", + "node_modules/@aws-sdk/crc64-nvme": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.5.tgz", + "integrity": "sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/protocol-http": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.0.tgz", - "integrity": "sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==", + "node_modules/@aws-sdk/crc64-nvme/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/querystring-builder": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.2.tgz", - "integrity": "sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==", + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.24.tgz", + "integrity": "sha512-FWg8uFmT6vQM7VuzELzwVo5bzExGaKHdubn0StjgrcU5FvuLExUe+k06kn/40uKv59rYzhez8eFNM4yYE/Yb/w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", - "@smithy/util-uri-escape": "^4.0.0", + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/querystring-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.2.tgz", - "integrity": "sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==", + "node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/service-error-classification": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.2.tgz", - "integrity": "sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ==", + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.26.tgz", + "integrity": "sha512-CY4ppZ+qHYqcXqBVi//sdHST1QK3KzOEiLtpLsc9W2k2vfZPKExGaQIsOwcyvjpjUEolotitmd3mUNY56IwDEA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0" + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/types": "^3.973.6", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/node-http-handler": "^4.5.1", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/util-stream": "^4.5.21", + "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.2.tgz", - "integrity": "sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.1.tgz", + "integrity": "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/signature-v4": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.0.tgz", - "integrity": "sha512-4t5WX60sL3zGJF/CtZsUQTs3UrZEDO2P7pEaElrekbLqkWPYkgqNW1oeiNYC6xXifBnT9dVBOnNQRvOE9riU9w==", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/smithy-client": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.2.0.tgz", - "integrity": "sha512-Qs65/w30pWV7LSFAez9DKy0Koaoh3iHhpcpCCJ4waj/iqwsuSzJna2+vYwq46yBaqO5ZbP9TjUsATUNxrKeBdw==", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.2.0", - "@smithy/middleware-endpoint": "^4.1.0", - "@smithy/middleware-stack": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "@smithy/util-stream": "^4.2.0", + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/types": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -1088,38 +1043,47 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/url-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.2.tgz", - "integrity": "sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.0.2", - "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/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==", + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.28", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.28.tgz", + "integrity": "sha512-wXYvq3+uQcZV7k+bE4yDXCTBdzWTU9x/nMiKBfzInmv6yYK1veMK0AKvRfRBd72nGWYKcL6AxwiPg9z/pYlgpw==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/credential-provider-env": "^3.972.24", + "@aws-sdk/credential-provider-http": "^3.972.26", + "@aws-sdk/credential-provider-login": "^3.972.28", + "@aws-sdk/credential-provider-process": "^3.972.24", + "@aws-sdk/credential-provider-sso": "^3.972.28", + "@aws-sdk/credential-provider-web-identity": "^3.972.28", + "@aws-sdk/nested-clients": "^3.996.18", + "@aws-sdk/types": "^3.973.6", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-ecs/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==", + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -1128,35 +1092,42 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/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==", + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.28", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.28.tgz", + "integrity": "sha512-ZSTfO6jqUTCysbdBPtEX5OUR//3rbD0lN7jO3sQeS2Gjr/Y+DT6SbIJ0oT2cemNw3UzKu97sNONd1CwNMthuZQ==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/nested-clients": "^3.996.18", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-ecs/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==", + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/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==", + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -1165,58 +1136,62 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.8.tgz", - "integrity": "sha512-ZTypzBra+lI/LfTYZeop9UjoJhhGRTg3pxrNpfSTQLd3AJ37r2z4AXTKpq1rFXiiUIJsYyFgNJdjWRGP/cbBaQ==", + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.29.tgz", + "integrity": "sha512-clSzDcvndpFJAggLDnDb36sPdlZYyEs5Zm6zgZjjUhwsJgSWiWKwFIXUVBcbruidNyBdbpOv2tNDL9sX8y3/0g==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "bowser": "^2.11.0", + "@aws-sdk/credential-provider-env": "^3.972.24", + "@aws-sdk/credential-provider-http": "^3.972.26", + "@aws-sdk/credential-provider-ini": "^3.972.28", + "@aws-sdk/credential-provider-process": "^3.972.24", + "@aws-sdk/credential-provider-sso": "^3.972.28", + "@aws-sdk/credential-provider-web-identity": "^3.972.28", + "@aws-sdk/types": "^3.973.6", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.8.tgz", - "integrity": "sha512-Rgk0Jc/UDfRTzVthye/k2dDsz5Xxs9LZaKCNPgJTRyoyBoeiNCnHsYGOyu1PKN+sDyPnJzMOz22JbwxzBp9NNA==", + "node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.1.0", - "@smithy/credential-provider-imds": "^4.0.2", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/util-endpoints": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.2.tgz", - "integrity": "sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ==", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.24.tgz", + "integrity": "sha512-Q2k/XLrFXhEztPHqj4SLCNID3hEPdlhh1CDLBpNnM+1L8fq7P+yON9/9M1IGN/dA5W45v44ylERfXtDAlmMNmw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-ecs/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==", + "node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -1225,56 +1200,59 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/util-middleware": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.2.tgz", - "integrity": "sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==", + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.28", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.28.tgz", + "integrity": "sha512-IoUlmKMLEITFn1SiCTjPfR6KrE799FBo5baWyk/5Ppar2yXZoUdaRqZzJzK6TcJxx450M8m8DbpddRVYlp5R/A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/nested-clients": "^3.996.18", + "@aws-sdk/token-providers": "3.1021.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/util-retry": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.2.tgz", - "integrity": "sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg==", + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.0.2", - "@smithy/types": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/util-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.0.tgz", - "integrity": "sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==", + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.28", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.28.tgz", + "integrity": "sha512-d+6h0SD8GGERzKe27v5rOzNGKOl0D+l0bWJdqrxH8WSQzHzjsQFIAPgIeOTUwBHVsKKwtSxc91K/SWax6XgswQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/node-http-handler": "^4.0.4", - "@smithy/types": "^4.2.0", - "@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", + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/nested-clients": "^3.996.18", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-ecs/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==", + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -1283,1015 +1261,762 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ecs/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==", + "node_modules/@aws-sdk/lib-storage": { + "version": "3.1023.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.1023.0.tgz", + "integrity": "sha512-1SFnmHlkKQgQxAt7/nK2f7b90kmymceojIbZT+yoSlHh2rJk2Dcjld8zo6lwUdfROrMwi4PP+z5nRMPG+d7zjQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "buffer": "5.6.0", + "events": "3.3.0", + "stream-browserify": "3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-s3": "^3.1023.0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/@smithy/util-waiter": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.3.tgz", - "integrity": "sha512-JtaY3FxmD+te+KSI2FJuEcfNC9T/DGGVf551babM7fAaXhjJUt7oSYurH1Devxd2+BOSUACCgt3buinx4UnmEA==", + "node_modules/@aws-sdk/lib-storage/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.654.0.tgz", - "integrity": "sha512-EsyeZJhkZD2VMdZpNt4NhlQ3QUAF24gMC+5w2wpGg6Yw+Bv7VLdg1t3PkTQovriJX1KTJAYHcGAuy92OFmWIng==", + "node_modules/@aws-sdk/lib-storage/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "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/client-sso-oidc": "3.654.0", - "@aws-sdk/client-sts": "3.654.0", - "@aws-sdk/core": "3.654.0", - "@aws-sdk/credential-provider-node": "3.654.0", - "@aws-sdk/middleware-bucket-endpoint": "3.654.0", - "@aws-sdk/middleware-expect-continue": "3.654.0", - "@aws-sdk/middleware-flexible-checksums": "3.654.0", - "@aws-sdk/middleware-host-header": "3.654.0", - "@aws-sdk/middleware-location-constraint": "3.654.0", - "@aws-sdk/middleware-logger": "3.654.0", - "@aws-sdk/middleware-recursion-detection": "3.654.0", - "@aws-sdk/middleware-sdk-s3": "3.654.0", - "@aws-sdk/middleware-ssec": "3.654.0", - "@aws-sdk/middleware-user-agent": "3.654.0", - "@aws-sdk/region-config-resolver": "3.654.0", - "@aws-sdk/signature-v4-multi-region": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-endpoints": "3.654.0", - "@aws-sdk/util-user-agent-browser": "3.654.0", - "@aws-sdk/util-user-agent-node": "3.654.0", - "@aws-sdk/xml-builder": "3.654.0", - "@smithy/config-resolver": "^3.0.8", - "@smithy/core": "^2.4.3", - "@smithy/eventstream-serde-browser": "^3.0.9", - "@smithy/eventstream-serde-config-resolver": "^3.0.6", - "@smithy/eventstream-serde-node": "^3.0.8", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/hash-blob-browser": "^3.1.5", - "@smithy/hash-node": "^3.0.6", - "@smithy/hash-stream-node": "^3.1.5", - "@smithy/invalid-dependency": "^3.0.6", - "@smithy/md5-js": "^3.0.6", - "@smithy/middleware-content-length": "^3.0.8", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-retry": "^3.0.18", - "@smithy/middleware-serde": "^3.0.6", - "@smithy/middleware-stack": "^3.0.6", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.18", - "@smithy/util-defaults-mode-node": "^3.0.18", - "@smithy/util-endpoints": "^2.1.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-retry": "^3.0.6", - "@smithy/util-stream": "^3.1.6", - "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.654.0.tgz", - "integrity": "sha512-4kBxs2IzCDtj6a6lRXa/lXK5wWpMGzwKtb+HMXf/rJYVM6x7wYRzc1hYrOd3DYkFQ/sR3dUFj+0mTP0os3aAbA==", + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.8.tgz", + "integrity": "sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.654.0", - "@aws-sdk/middleware-host-header": "3.654.0", - "@aws-sdk/middleware-logger": "3.654.0", - "@aws-sdk/middleware-recursion-detection": "3.654.0", - "@aws-sdk/middleware-user-agent": "3.654.0", - "@aws-sdk/region-config-resolver": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-endpoints": "3.654.0", - "@aws-sdk/util-user-agent-browser": "3.654.0", - "@aws-sdk/util-user-agent-node": "3.654.0", - "@smithy/config-resolver": "^3.0.8", - "@smithy/core": "^2.4.3", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/hash-node": "^3.0.6", - "@smithy/invalid-dependency": "^3.0.6", - "@smithy/middleware-content-length": "^3.0.8", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-retry": "^3.0.18", - "@smithy/middleware-serde": "^3.0.6", - "@smithy/middleware-stack": "^3.0.6", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.18", - "@smithy/util-defaults-mode-node": "^3.0.18", - "@smithy/util-endpoints": "^2.1.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-retry": "^3.0.6", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.654.0.tgz", - "integrity": "sha512-kogsx3Ql81JouHS7DkheCDU9MYAvK0AokxjcshDveGmf7BbgbWCA8Fnb9wjQyNDaOXNvkZu8Z8rgkX91z324/w==", + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/types": "^3.4.2", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.654.0.tgz", - "integrity": "sha512-tgmAH4MBi/aDR882lfw48+tDV95ZH3GWc1Eoe6DpNLiM3GN2VfU/cZwuHmi6aq+vAbdIlswBHJ/+va0fOvlyjw==", + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/property-provider": "^3.1.6", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/util-stream": "^3.1.6", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.654.0.tgz", - "integrity": "sha512-wPV7CNYaXDEc+SS+3R0v8SZwkHRUE1z2k2j1d49tH5QBDT4tb/k2V/biXWkwSk3hbR+IMWXmuhJDv/5lybhIvg==", + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.8.tgz", + "integrity": "sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.654.0", - "@aws-sdk/credential-provider-http": "3.654.0", - "@aws-sdk/credential-provider-ini": "3.654.0", - "@aws-sdk/credential-provider-process": "3.654.0", - "@aws-sdk/credential-provider-sso": "3.654.0", - "@aws-sdk/credential-provider-web-identity": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/credential-provider-imds": "^3.2.3", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", + "@aws-sdk/types": "^3.973.6", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.654.0.tgz", - "integrity": "sha512-DKSdaNu2hwdmuvnm9KnA0NLqMWxxmxSOLWjSUSoFIm++wGXUjPrRMFYKvMktaXnPuyf5my8gF/yGbwzPZ8wlTg==", + "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.654.0", - "@aws-sdk/credential-provider-http": "3.654.0", - "@aws-sdk/credential-provider-process": "3.654.0", - "@aws-sdk/credential-provider-sso": "3.654.0", - "@aws-sdk/credential-provider-web-identity": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/credential-provider-imds": "^3.2.3", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.654.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.654.0.tgz", - "integrity": "sha512-6a2g9gMtZToqSu+CusjNK5zvbLJahQ9di7buO3iXgbizXpLXU1rnawCpWxwslMpT5fLgMSKDnKDrr6wdEk7jSw==", + "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/types": "^3.4.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.654.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.654.0.tgz", - "integrity": "sha512-PmQoo8sZ9Q2Ow8OMzK++Z9lI7MsRUG7sNq3E72DVA215dhtTICTDQwGlXH2AAmIp7n+G9LLRds+4wo2ehG4mkg==", + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.974.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.6.tgz", + "integrity": "sha512-YckB8k1ejbyCg/g36gUMFLNzE4W5cERIa4MtsdO+wpTmJEP0+TB7okWIt7d8TDOvnb7SwvxJ21E4TGOBxFpSWQ==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/crc64-nvme": "^3.972.5", + "@aws-sdk/types": "^3.973.6", + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-stream": "^4.5.21", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.654.0.tgz", - "integrity": "sha512-7GFme6fWEdA/XYKzZPOAdj/jS6fMBy1NdSIZsDXikS0v9jU+ZzHrAaWt13YLzHyjgxB9Sg9id9ncdY1IiubQXQ==", + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.654.0", - "@aws-sdk/token-providers": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.654.0.tgz", - "integrity": "sha512-D8GeJYmvbfWkQDtTB4owmIobSMexZel0fOoetwvgCQ/7L8VPph3Q2bn1TRRIXvH7wdt6DcDxA3tKMHPBkT3GlA==", + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.654.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/abort-controller": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.4.tgz", - "integrity": "sha512-VupaALAQlXViW3/enTf/f5l5JZYSAxoJL7f0nanhNNKnww6DGCg1oYIuNP78KDugnkwthBO6iEcym16HhWV8RQ==", + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/node-http-handler": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.3.tgz", - "integrity": "sha512-/gcm5DJ3k1b1zEInzBGAZC8ntJ+jwrz1NcSIu+9dSXd1FfG0G6QgkDI40tt8/WYUbHtLyo8fEqtm2v29koWo/w==", + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^3.1.4", - "@smithy/protocol-http": "^4.1.3", - "@smithy/querystring-builder": "^3.0.6", - "@smithy/types": "^3.4.2", + "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/querystring-builder": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.6.tgz", - "integrity": "sha512-sQe08RunoObe+Usujn9+R2zrLuQERi3CWvRO3BvnoWSYUaIrLKuAIeY7cMeDax6xGyfIP3x/yFWbEKSXvOnvVg==", + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", - "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.8.tgz", + "integrity": "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==", + "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "node_modules/@aws-sdk/middleware-host-header/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "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==", + "node_modules/@aws-sdk/middleware-host-header/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "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/client-sso-oidc": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.654.0.tgz", - "integrity": "sha512-gbHrKsEnaAtmkNCVQzLyiqMzpDaThV/bWl/ODEklI+t6stW3Pe3oDMstEHLfJ6JU5g8sYnx4VLuxlnJMtUkvPw==", + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.8.tgz", + "integrity": "sha512-KaUoFuoFPziIa98DSQsTPeke1gvGXlc5ZGMhy+b+nLxZ4A7jmJgLzjEF95l8aOQN2T/qlPP3MrAyELm8ExXucw==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.654.0", - "@aws-sdk/credential-provider-node": "3.654.0", - "@aws-sdk/middleware-host-header": "3.654.0", - "@aws-sdk/middleware-logger": "3.654.0", - "@aws-sdk/middleware-recursion-detection": "3.654.0", - "@aws-sdk/middleware-user-agent": "3.654.0", - "@aws-sdk/region-config-resolver": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-endpoints": "3.654.0", - "@aws-sdk/util-user-agent-browser": "3.654.0", - "@aws-sdk/util-user-agent-node": "3.654.0", - "@smithy/config-resolver": "^3.0.8", - "@smithy/core": "^2.4.3", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/hash-node": "^3.0.6", - "@smithy/invalid-dependency": "^3.0.6", - "@smithy/middleware-content-length": "^3.0.8", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-retry": "^3.0.18", - "@smithy/middleware-serde": "^3.0.6", - "@smithy/middleware-stack": "^3.0.6", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.18", - "@smithy/util-defaults-mode-node": "^3.0.18", - "@smithy/util-endpoints": "^2.1.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-retry": "^3.0.6", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.654.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/client-sso": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.654.0.tgz", - "integrity": "sha512-4kBxs2IzCDtj6a6lRXa/lXK5wWpMGzwKtb+HMXf/rJYVM6x7wYRzc1hYrOd3DYkFQ/sR3dUFj+0mTP0os3aAbA==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.654.0", - "@aws-sdk/middleware-host-header": "3.654.0", - "@aws-sdk/middleware-logger": "3.654.0", - "@aws-sdk/middleware-recursion-detection": "3.654.0", - "@aws-sdk/middleware-user-agent": "3.654.0", - "@aws-sdk/region-config-resolver": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-endpoints": "3.654.0", - "@aws-sdk/util-user-agent-browser": "3.654.0", - "@aws-sdk/util-user-agent-node": "3.654.0", - "@smithy/config-resolver": "^3.0.8", - "@smithy/core": "^2.4.3", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/hash-node": "^3.0.6", - "@smithy/invalid-dependency": "^3.0.6", - "@smithy/middleware-content-length": "^3.0.8", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-retry": "^3.0.18", - "@smithy/middleware-serde": "^3.0.6", - "@smithy/middleware-stack": "^3.0.6", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.18", - "@smithy/util-defaults-mode-node": "^3.0.18", - "@smithy/util-endpoints": "^2.1.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-retry": "^3.0.6", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.654.0.tgz", - "integrity": "sha512-kogsx3Ql81JouHS7DkheCDU9MYAvK0AokxjcshDveGmf7BbgbWCA8Fnb9wjQyNDaOXNvkZu8Z8rgkX91z324/w==", + "node_modules/@aws-sdk/middleware-location-constraint/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/types": "^3.4.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.654.0.tgz", - "integrity": "sha512-tgmAH4MBi/aDR882lfw48+tDV95ZH3GWc1Eoe6DpNLiM3GN2VfU/cZwuHmi6aq+vAbdIlswBHJ/+va0fOvlyjw==", + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.8.tgz", + "integrity": "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/property-provider": "^3.1.6", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/util-stream": "^3.1.6", + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.654.0.tgz", - "integrity": "sha512-DKSdaNu2hwdmuvnm9KnA0NLqMWxxmxSOLWjSUSoFIm++wGXUjPrRMFYKvMktaXnPuyf5my8gF/yGbwzPZ8wlTg==", + "node_modules/@aws-sdk/middleware-logger/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.654.0", - "@aws-sdk/credential-provider-http": "3.654.0", - "@aws-sdk/credential-provider-process": "3.654.0", - "@aws-sdk/credential-provider-sso": "3.654.0", - "@aws-sdk/credential-provider-web-identity": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/credential-provider-imds": "^3.2.3", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.654.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.654.0.tgz", - "integrity": "sha512-wPV7CNYaXDEc+SS+3R0v8SZwkHRUE1z2k2j1d49tH5QBDT4tb/k2V/biXWkwSk3hbR+IMWXmuhJDv/5lybhIvg==", + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.9.tgz", + "integrity": "sha512-/Wt5+CT8dpTFQxEJ9iGy/UGrXr7p2wlIOEHvIr/YcHYByzoLjrqkYqXdJjd9UIgWjv7eqV2HnFJen93UTuwfTQ==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.654.0", - "@aws-sdk/credential-provider-http": "3.654.0", - "@aws-sdk/credential-provider-ini": "3.654.0", - "@aws-sdk/credential-provider-process": "3.654.0", - "@aws-sdk/credential-provider-sso": "3.654.0", - "@aws-sdk/credential-provider-web-identity": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/credential-provider-imds": "^3.2.3", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", + "@aws-sdk/types": "^3.973.6", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.654.0.tgz", - "integrity": "sha512-PmQoo8sZ9Q2Ow8OMzK++Z9lI7MsRUG7sNq3E72DVA215dhtTICTDQwGlXH2AAmIp7n+G9LLRds+4wo2ehG4mkg==", + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.654.0.tgz", - "integrity": "sha512-7GFme6fWEdA/XYKzZPOAdj/jS6fMBy1NdSIZsDXikS0v9jU+ZzHrAaWt13YLzHyjgxB9Sg9id9ncdY1IiubQXQ==", + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.654.0", - "@aws-sdk/token-providers": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.654.0.tgz", - "integrity": "sha512-6a2g9gMtZToqSu+CusjNK5zvbLJahQ9di7buO3iXgbizXpLXU1rnawCpWxwslMpT5fLgMSKDnKDrr6wdEk7jSw==", + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.27.tgz", + "integrity": "sha512-gomO6DZwx+1D/9mbCpcqO5tPBqYBK7DtdgjTIjZ4yvfh/S7ETwAPS0XbJgP2JD8Ycr5CwVrEkV1sFtu3ShXeOw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/types": "^3.4.2", + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/core": "^3.23.13", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/signature-v4": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-stream": "^4.5.21", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.654.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/token-providers": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.654.0.tgz", - "integrity": "sha512-D8GeJYmvbfWkQDtTB4owmIobSMexZel0fOoetwvgCQ/7L8VPph3Q2bn1TRRIXvH7wdt6DcDxA3tKMHPBkT3GlA==", + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.654.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/abort-controller": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.4.tgz", - "integrity": "sha512-VupaALAQlXViW3/enTf/f5l5JZYSAxoJL7f0nanhNNKnww6DGCg1oYIuNP78KDugnkwthBO6iEcym16HhWV8RQ==", + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/node-http-handler": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.3.tgz", - "integrity": "sha512-/gcm5DJ3k1b1zEInzBGAZC8ntJ+jwrz1NcSIu+9dSXd1FfG0G6QgkDI40tt8/WYUbHtLyo8fEqtm2v29koWo/w==", + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^3.1.4", - "@smithy/protocol-http": "^4.1.3", - "@smithy/querystring-builder": "^3.0.6", - "@smithy/types": "^3.4.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/querystring-builder": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.6.tgz", - "integrity": "sha512-sQe08RunoObe+Usujn9+R2zrLuQERi3CWvRO3BvnoWSYUaIrLKuAIeY7cMeDax6xGyfIP3x/yFWbEKSXvOnvVg==", + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", - "@smithy/util-uri-escape": "^3.0.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.8.tgz", + "integrity": "sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw==", + "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/middleware-ssec/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "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/client-sso/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==", + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.28", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.28.tgz", + "integrity": "sha512-cfWZFlVh7Va9lRay4PN2A9ARFzaBYcA097InT5M2CdRS05ECF5yaz86jET8Wsl2WcyKYEvVr/QNmKtYtafUHtQ==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@smithy/core": "^3.23.13", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-retry": "^4.2.13", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/nested-clients": { + "version": "3.996.18", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.18.tgz", + "integrity": "sha512-c7ZSIXrESxHKx2Mcopgd8AlzZgoXMr20fkx5ViPWPOLBvmyhw9VwJx/Govg8Ef/IhEon5R9l53Z8fdYSEmp6VA==", + "license": "Apache-2.0", "dependencies": { - "@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" - }, - "engines": { - "node": ">=18.0.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.9", + "@aws-sdk/middleware-user-agent": "^3.972.28", + "@aws-sdk/region-config-resolver": "^3.972.10", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.14", + "@smithy/config-resolver": "^4.4.13", + "@smithy/core": "^3.23.13", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/middleware-retry": "^4.4.46", + "@smithy/middleware-serde": "^4.2.16", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.1", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.44", + "@smithy/util-defaults-mode-node": "^4.2.48", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.13", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "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" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.1.tgz", + "integrity": "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-endpoints": "^3.0.6", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", + "license": "Apache-2.0", "dependencies": { - "@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", + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.0.8", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.3", - "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "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", + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.10.tgz", + "integrity": "sha512-1dq9ToC6e070QvnVhhbAs3bb5r6cQ10gTVc6cyRV5uvQe7P138TV2uG2i6+Yok4bAkVAcx5AqkTEBUvWEtBlsQ==", + "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", + "@aws-sdk/types": "^3.973.6", + "@smithy/config-resolver": "^4.4.13", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/s3-presigned-post": { + "version": "3.1023.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-presigned-post/-/s3-presigned-post-3.1023.0.tgz", + "integrity": "sha512-cZ6WLQbIpHd1OHsXMUWR62el3uIZvDxSnpHiGFJ3kke+Pm5nzyOD02Q5yWzJWrTHJMedpzbUl4LfVfTpoyKslA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@aws-sdk/client-s3": "3.1023.0", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-format-url": "^3.972.8", + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/signature-v4": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/s3-presigned-post/node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -2299,269 +2024,274 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/s3-presigned-post/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/s3-presigned-post/node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@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", + "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/s3-presigned-post/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "dev": true, + "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" + "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/s3-presigned-post/node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/s3-request-presigner": { + "version": "3.1024.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.1024.0.tgz", + "integrity": "sha512-KNoGsXnTfBxPqrtV2Owd4mrLnhTHRsOz6Hdaz+As5czGMQuPchoRvG1BJzn2NFRGLt/HSJAXVn+G6IhtRIDe+Q==", "dependencies": { - "@smithy/types": "^4.3.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.15", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-format-url": "^3.972.8", + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.15.tgz", + "integrity": "sha512-Ukw2RpqvaL96CjfH/FgfBmy/ZosHBqoHBCFsN61qGg99F33vpntIVii8aNeh65XuOja73arSduskoa4OJea9RQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@aws-sdk/middleware-sdk-s3": "^3.972.27", + "@aws-sdk/types": "^3.973.6", + "@smithy/protocol-http": "^5.3.12", + "@smithy/signature-v4": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/token-providers": { + "version": "3.1021.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1021.0.tgz", + "integrity": "sha512-TKY6h9spUk3OLs5v1oAgW9mAeBE3LAGNBwJokLy96wwmd4W2v/tYlXseProyed9ValDj2u1jK/4Rg1T+1NXyJA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/nested-clients": "^3.996.18", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/token-providers/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1" + "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/types": { + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.6.tgz", + "integrity": "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/types/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.3.tgz", + "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==", + "license": "Apache-2.0", "dependencies": { - "@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.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", + "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", + "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/util-endpoints/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/util-format-url": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.8.tgz", + "integrity": "sha512-J6DS9oocrgxM8xlUTTmQOuwRF6rnAGEujAN9SAzllcrQmwn5iJ58ogxy3SEhD0Q7JZvlA5jvIXBkpQRqEqlE9A==", + "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/util-format-url/node_modules/@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", + "license": "Apache-2.0", "dependencies": { + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/util-format-url/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -2569,78 +2299,83 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/util-format-url/node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", + "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.8.tgz", + "integrity": "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", "bowser": "^2.11.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/util-user-agent-browser/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.1.4", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.14.tgz", + "integrity": "sha512-vNSB/DYaPOyujVZBg/zUznH9QC142MaTHVmaFlF7uzzfg3CgT9f/l4C0Yi+vU/tbBhxVcXVB90Oohk5+o+ZbWw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", + "@aws-sdk/middleware-user-agent": "^3.972.28", + "@aws-sdk/types": "^3.973.6", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -2648,8874 +2383,9039 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.16.tgz", + "integrity": "sha512-iu2pyvaqmeatIJLURLqx9D+4jKAdTH20ntzB6BFwjyN7V960r4jK32mx0Zf7YbtOYAbmbtQfDNuL60ONinyw7A==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.13.1", + "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", + "node_modules/@aws-sdk/xml-builder/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.0.6", - "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", - "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" - }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "license": "Apache-2.0", "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/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==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-sso/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==", - "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-sso/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" - } - ], + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", "dependencies": { - "strnum": "^2.1.0" + "@babel/types": "^7.29.0" }, "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/@aws-sdk/client-sso/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" - } - ] - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.654.0.tgz", - "integrity": "sha512-tyHa8jsBy+/NQZFHm6Q2Q09Vi9p3EH4yPy6PU8yPewpi2klreObtrUd0anJa6nzjS9SSuqnlZWsRic3cQ4QwCg==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.654.0", - "@aws-sdk/core": "3.654.0", - "@aws-sdk/credential-provider-node": "3.654.0", - "@aws-sdk/middleware-host-header": "3.654.0", - "@aws-sdk/middleware-logger": "3.654.0", - "@aws-sdk/middleware-recursion-detection": "3.654.0", - "@aws-sdk/middleware-user-agent": "3.654.0", - "@aws-sdk/region-config-resolver": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-endpoints": "3.654.0", - "@aws-sdk/util-user-agent-browser": "3.654.0", - "@aws-sdk/util-user-agent-node": "3.654.0", - "@smithy/config-resolver": "^3.0.8", - "@smithy/core": "^2.4.3", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/hash-node": "^3.0.6", - "@smithy/invalid-dependency": "^3.0.6", - "@smithy/middleware-content-length": "^3.0.8", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-retry": "^3.0.18", - "@smithy/middleware-serde": "^3.0.6", - "@smithy/middleware-stack": "^3.0.6", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.18", - "@smithy/util-defaults-mode-node": "^3.0.18", - "@smithy/util-endpoints": "^2.1.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-retry": "^3.0.6", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=16.0.0" + "node": ">=6.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/client-sso": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.654.0.tgz", - "integrity": "sha512-4kBxs2IzCDtj6a6lRXa/lXK5wWpMGzwKtb+HMXf/rJYVM6x7wYRzc1hYrOd3DYkFQ/sR3dUFj+0mTP0os3aAbA==", + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.654.0", - "@aws-sdk/middleware-host-header": "3.654.0", - "@aws-sdk/middleware-logger": "3.654.0", - "@aws-sdk/middleware-recursion-detection": "3.654.0", - "@aws-sdk/middleware-user-agent": "3.654.0", - "@aws-sdk/region-config-resolver": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-endpoints": "3.654.0", - "@aws-sdk/util-user-agent-browser": "3.654.0", - "@aws-sdk/util-user-agent-node": "3.654.0", - "@smithy/config-resolver": "^3.0.8", - "@smithy/core": "^2.4.3", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/hash-node": "^3.0.6", - "@smithy/invalid-dependency": "^3.0.6", - "@smithy/middleware-content-length": "^3.0.8", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-retry": "^3.0.18", - "@smithy/middleware-serde": "^3.0.6", - "@smithy/middleware-stack": "^3.0.6", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.18", - "@smithy/util-defaults-mode-node": "^3.0.18", - "@smithy/util-endpoints": "^2.1.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-retry": "^3.0.6", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" + "regenerator-runtime": "^0.14.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.654.0.tgz", - "integrity": "sha512-kogsx3Ql81JouHS7DkheCDU9MYAvK0AokxjcshDveGmf7BbgbWCA8Fnb9wjQyNDaOXNvkZu8Z8rgkX91z324/w==", + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { - "node": ">=16.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.654.0.tgz", - "integrity": "sha512-tgmAH4MBi/aDR882lfw48+tDV95ZH3GWc1Eoe6DpNLiM3GN2VfU/cZwuHmi6aq+vAbdIlswBHJ/+va0fOvlyjw==", - "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/property-provider": "^3.1.6", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/util-stream": "^3.1.6", - "tslib": "^2.6.2" + "node_modules/@biomejs/biome": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.4.tgz", + "integrity": "sha512-tigwWS5KfJf0cABVd52NVaXyAVv4qpUXOWJ1rxFL8xF1RVoeS2q/LK+FHgYoKMclJCuRoCWAPy1IXaN9/mS61Q==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.654.0.tgz", - "integrity": "sha512-DKSdaNu2hwdmuvnm9KnA0NLqMWxxmxSOLWjSUSoFIm++wGXUjPrRMFYKvMktaXnPuyf5my8gF/yGbwzPZ8wlTg==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.654.0", - "@aws-sdk/credential-provider-http": "3.654.0", - "@aws-sdk/credential-provider-process": "3.654.0", - "@aws-sdk/credential-provider-sso": "3.654.0", - "@aws-sdk/credential-provider-web-identity": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/credential-provider-imds": "^3.2.3", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "node": ">=14.21.3" }, - "engines": { - "node": ">=16.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.654.0" + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.4.4", + "@biomejs/cli-darwin-x64": "2.4.4", + "@biomejs/cli-linux-arm64": "2.4.4", + "@biomejs/cli-linux-arm64-musl": "2.4.4", + "@biomejs/cli-linux-x64": "2.4.4", + "@biomejs/cli-linux-x64-musl": "2.4.4", + "@biomejs/cli-win32-arm64": "2.4.4", + "@biomejs/cli-win32-x64": "2.4.4" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.4.tgz", + "integrity": "sha512-jZ+Xc6qvD6tTH5jM6eKX44dcbyNqJHssfl2nnwT6vma6B1sj7ZLTGIk6N5QwVBs5xGN52r3trk5fgd3sQ9We9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.654.0.tgz", - "integrity": "sha512-wPV7CNYaXDEc+SS+3R0v8SZwkHRUE1z2k2j1d49tH5QBDT4tb/k2V/biXWkwSk3hbR+IMWXmuhJDv/5lybhIvg==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.654.0", - "@aws-sdk/credential-provider-http": "3.654.0", - "@aws-sdk/credential-provider-ini": "3.654.0", - "@aws-sdk/credential-provider-process": "3.654.0", - "@aws-sdk/credential-provider-sso": "3.654.0", - "@aws-sdk/credential-provider-web-identity": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/credential-provider-imds": "^3.2.3", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.4.tgz", + "integrity": "sha512-Dh1a/+W+SUCXhEdL7TiX3ArPTFCQKJTI1mGncZNWfO+6suk+gYA4lNyJcBB+pwvF49uw0pEbUS49BgYOY4hzUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=16.0.0" + "node": ">=14.21.3" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.654.0.tgz", - "integrity": "sha512-PmQoo8sZ9Q2Ow8OMzK++Z9lI7MsRUG7sNq3E72DVA215dhtTICTDQwGlXH2AAmIp7n+G9LLRds+4wo2ehG4mkg==", - "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.4.tgz", + "integrity": "sha512-V/NFfbWhsUU6w+m5WYbBenlEAz8eYnSqRMDMAW3K+3v0tYVkNyZn8VU0XPxk/lOqNXLSCCrV7FmV/u3SjCBShg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16.0.0" + "node": ">=14.21.3" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.654.0.tgz", - "integrity": "sha512-7GFme6fWEdA/XYKzZPOAdj/jS6fMBy1NdSIZsDXikS0v9jU+ZzHrAaWt13YLzHyjgxB9Sg9id9ncdY1IiubQXQ==", - "dependencies": { - "@aws-sdk/client-sso": "3.654.0", - "@aws-sdk/token-providers": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.4.tgz", + "integrity": "sha512-+sPAXq3bxmFwhVFJnSwkSF5Rw2ZAJMH3MF6C9IveAEOdSpgajPhoQhbbAK12SehN9j2QrHpk4J/cHsa/HqWaYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16.0.0" + "node": ">=14.21.3" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.654.0.tgz", - "integrity": "sha512-D8GeJYmvbfWkQDtTB4owmIobSMexZel0fOoetwvgCQ/7L8VPph3Q2bn1TRRIXvH7wdt6DcDxA3tKMHPBkT3GlA==", - "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.4.tgz", + "integrity": "sha512-R4+ZCDtG9kHArasyBO+UBD6jr/FcFCTH8QkNTOCu0pRJzCWyWC4EtZa2AmUZB5h3e0jD7bRV2KvrENcf8rndBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.654.0" + "node": ">=14.21.3" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.654.0.tgz", - "integrity": "sha512-6a2g9gMtZToqSu+CusjNK5zvbLJahQ9di7buO3iXgbizXpLXU1rnawCpWxwslMpT5fLgMSKDnKDrr6wdEk7jSw==", - "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.4.tgz", + "integrity": "sha512-gGvFTGpOIQDb5CQ2VC0n9Z2UEqlP46c4aNgHmAMytYieTGEcfqhfCFnhs6xjt0S3igE6q5GLuIXtdQt3Izok+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.654.0" + "node": ">=14.21.3" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/abort-controller": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.4.tgz", - "integrity": "sha512-VupaALAQlXViW3/enTf/f5l5JZYSAxoJL7f0nanhNNKnww6DGCg1oYIuNP78KDugnkwthBO6iEcym16HhWV8RQ==", - "dependencies": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.4.tgz", + "integrity": "sha512-trzCqM7x+Gn832zZHgr28JoYagQNX4CZkUZhMUac2YxvvyDRLJDrb5m9IA7CaZLlX6lTQmADVfLEKP1et1Ma4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=16.0.0" + "node": ">=14.21.3" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/node-http-handler": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.3.tgz", - "integrity": "sha512-/gcm5DJ3k1b1zEInzBGAZC8ntJ+jwrz1NcSIu+9dSXd1FfG0G6QgkDI40tt8/WYUbHtLyo8fEqtm2v29koWo/w==", - "dependencies": { - "@smithy/abort-controller": "^3.1.4", - "@smithy/protocol-http": "^4.1.3", - "@smithy/querystring-builder": "^3.0.6", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.4.tgz", + "integrity": "sha512-gnOHKVPFAAPrpoPt2t+Q6FZ7RPry/FDV3GcpU53P3PtLNnQjBmKyN2Vh/JtqXet+H4pme8CC76rScwdjDcT1/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=16.0.0" + "node": ">=14.21.3" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", + "node_modules/@datadog/pprof": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@datadog/pprof/-/pprof-5.14.0.tgz", + "integrity": "sha512-bXYptMf0BY3NdKcAmX5aAOnozj7GFdNYE4SIdPF0/Q/Yfb2fYBIYZaN/Le6BhiDIepBvvH/H75d3EIFhB64kOw==", + "hasInstallScript": true, "dependencies": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "node-gyp-build": "<4.0", + "pprof-format": "^2.2.1", + "source-map": "^0.7.4" }, "engines": { - "node": ">=16.0.0" + "node": ">=16" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/querystring-builder": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.6.tgz", - "integrity": "sha512-sQe08RunoObe+Usujn9+R2zrLuQERi3CWvRO3BvnoWSYUaIrLKuAIeY7cMeDax6xGyfIP3x/yFWbEKSXvOnvVg==", - "dependencies": { - "@smithy/types": "^3.4.2", - "@smithy/util-uri-escape": "^3.0.0", - "tslib": "^2.6.2" - }, + "node_modules/@datadog/pprof/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "engines": { - "node": ">=16.0.0" + "node": ">= 12" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "tslib": "^2.4.0" } }, - "node_modules/@aws-sdk/core": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.654.0.tgz", - "integrity": "sha512-4Rwx7BVaNaFqmXBDmnOkMbyuIFFbpZ+ru4lr660p45zY1QoNNSalechfoRffcokLFOZO+VWEJkdcorPUUU993w==", + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "@smithy/core": "^2.4.3", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/property-provider": "^3.1.6", - "@smithy/protocol-http": "^4.1.3", - "@smithy/signature-v4": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/util-middleware": "^3.0.6", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "tslib": "^2.4.0" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", - "dependencies": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=16.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/core/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=16.0.0" + "node": ">=18" } }, - "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==", - "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" - }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "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" - }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", - "dependencies": { - "@smithy/middleware-serde": "^4.0.8", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.3", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "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" - }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@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" - }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "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" - }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "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" - }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@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" - }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "node_modules/@fastify/accept-negotiator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", + "integrity": "sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" }, - "node_modules/@aws-sdk/credential-provider-env/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==", + "node_modules/@fastify/accepts": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@fastify/accepts/-/accepts-5.0.4.tgz", + "integrity": "sha512-XKtvD77ZLQ/4G5r1WhPua5+rTctt16DF4XUMBQuP8KM/Ic431GhfqjJoYvwS4aDaUhoRTiU9DGFaMZ3TRM6ctg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "accepts": "^1.3.8", + "fastify-plugin": "^5.0.0" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", + "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "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": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env/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==", + "node_modules/@fastify/basic-auth": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@fastify/basic-auth/-/basic-auth-6.2.0.tgz", + "integrity": "sha512-Ao9Jf8TyW8v7p3CPy++c+E3qcCDeWfAlSIfFo0CsKrfvm81i0OCpnobIMwaSSkg/At0rzsLzbJPDWrgNru0G1w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@fastify/error": "^4.0.0", + "fastify-plugin": "^5.0.0" } }, - "node_modules/@aws-sdk/credential-provider-env/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==", - "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "node_modules/@fastify/busboy": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.1.tgz", + "integrity": "sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==", + "license": "MIT" }, - "node_modules/@aws-sdk/credential-provider-env/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==", + "node_modules/@fastify/deepmerge": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-3.2.1.tgz", + "integrity": "sha512-N5Oqvltoa2r9z1tbx4xjky0oRR60v+T47Ic4J1ukoVQcptLOrIdRnCSdTGmOmajZuHVKlTnfcmrjyqsGEW1ztA==", "funding": [ { "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" } ], - "dependencies": { - "strnum": "^2.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } + "license": "MIT" }, - "node_modules/@aws-sdk/credential-provider-env/node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", "funding": [ { "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" } - ] + ], + "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==", + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", + "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "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": ">=18.0.0" + "fast-json-stringify": "^6.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@fastify/forwarded": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", + "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "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" + "dequal": "^2.0.3" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@fastify/multipart": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-9.4.0.tgz", + "integrity": "sha512-Z404bzZeLSXTBmp/trCBuoVFX28pM7rhv849Q5TsbTFZHuk1lc4QjQITTPK92DKVpXmNtJXeHSSc7GYvqFpxAQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@fastify/busboy": "^3.0.0", + "@fastify/deepmerge": "^3.0.0", + "@fastify/error": "^4.0.0", + "fastify-plugin": "^5.0.0", + "secure-json-parse": "^4.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@fastify/otel": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@fastify/otel/-/otel-0.18.1.tgz", + "integrity": "sha512-7TYQXrOBRwCuTiwQm/2qCPO37af011934clxBj6F7KyF9a2a9MB+aSrWv8vMVp5N8rZC1AQ4pUp8uDs5Z40UPQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.28.0", + "minimatch": "^10.2.4" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/abort-controller": { + "node_modules/@fastify/otel/node_modules/balanced-match": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "engines": { - "node": ">=18.0.0" + "node": "18 || 20 || >=22" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", + "node_modules/@fastify/otel/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dependencies": { - "@smithy/middleware-serde": "^4.0.8", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.3", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=18.0.0" + "node": "18 || 20 || >=22" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@fastify/otel/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "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" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=18.0.0" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@fastify/proxy-addr": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@fastify/rate-limit": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@fastify/rate-limit/-/rate-limit-10.3.0.tgz", + "integrity": "sha512-eIGkG9XKQs0nyynatApA3EVrojHOuq4l6fhB4eeCk4PIOeadvOJz9/4w3vGI44Go17uaXOWEcPkaD8kuKm7g6Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "@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.0.0" + "@lukeed/ms": "^2.0.2", + "fastify-plugin": "^5.0.0", + "toad-cache": "^3.7.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@fastify/send": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@fastify/send/-/send-4.1.0.tgz", + "integrity": "sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@lukeed/ms": "^2.0.2", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "^2.0.0", + "mime": "^3" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@fastify/static": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-9.0.0.tgz", + "integrity": "sha512-r64H8Woe/vfilg5RTy7lwWlE8ZZcTrc3kebYFMEUBrMqlydhQyoiExQXdYAy2REVpST/G35+stAM8WYp1WGmMA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@fastify/accept-negotiator": "^2.0.0", + "@fastify/send": "^4.0.0", + "content-disposition": "^1.0.1", + "fastify-plugin": "^5.0.0", + "fastq": "^1.17.1", + "glob": "^13.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@fastify/swagger": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-9.7.0.tgz", + "integrity": "sha512-Vp1SC1GC2Hrkd3faFILv86BzUNyFz5N4/xdExqtCgkGASOzn/x+eMe4qXIGq7cdT6wif/P/oa6r1Ruqx19paZA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "fastify-plugin": "^5.0.0", + "json-schema-resolver": "^3.0.0", + "openapi-types": "^12.1.3", + "rfdc": "^1.3.1", + "yaml": "^2.4.2" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@fastify/swagger-ui": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@fastify/swagger-ui/-/swagger-ui-5.2.5.tgz", + "integrity": "sha512-ky3I0LAkXKX/prwSDpoQ3kscBKsj2Ha6Gp1/JfgQSqyx0bm9F2bE//XmGVGj2cR9l5hUjZYn60/hqn7e+OLgWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "@smithy/abort-controller": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@fastify/static": "^9.0.0", + "fastify-plugin": "^5.0.0", + "openapi-types": "^12.1.3", + "rfdc": "^1.3.1", + "yaml": "^2.4.1" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@fastify/websocket": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@fastify/websocket/-/websocket-11.2.0.tgz", + "integrity": "sha512-3HrDPbAG1CzUCqnslgJxppvzaAZffieOVbLp1DAy1huCSynUWPifSvfdEDUR8HlJLp3sp1A36uOM2tJogADS8w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "duplexify": "^4.1.3", + "fastify-plugin": "^5.0.0", + "ws": "^8.16.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=12.10.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" }, "engines": { - "node": ">=18.0.0" + "node": ">=6" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", + "license": "ISC" + }, + "node_modules/@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", "engines": { - "node": ">=18.0.0" + "node": ">=12" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, "engines": { - "node": ">=18.0.0" + "node": ">=6.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", "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" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@kubernetes/client-node": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-1.3.0.tgz", + "integrity": "sha512-IE0yrIpOT97YS5fg2QpzmPzm8Wmcdf4ueWMn+FiJSI3jgTTQT1u+LUhoYpdfhdHAVxdrNsaBg2C0UXSnOgMoCQ==", + "license": "Apache-2.0", "dependencies": { - "@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.0.0" + "@types/js-yaml": "^4.0.1", + "@types/node": "^22.0.0", + "@types/node-fetch": "^2.6.9", + "@types/stream-buffers": "^3.0.3", + "form-data": "^4.0.0", + "hpagent": "^1.2.0", + "isomorphic-ws": "^5.0.0", + "js-yaml": "^4.1.0", + "jsonpath-plus": "^10.3.0", + "node-fetch": "^2.6.9", + "openid-client": "^6.1.3", + "rfc4648": "^1.3.0", + "socks-proxy-agent": "^8.0.4", + "stream-buffers": "^3.0.2", + "tar-fs": "^3.0.8", + "ws": "^8.18.2" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@kubernetes/client-node/node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" - }, + "undici-types": "~6.21.0" + } + }, + "node_modules/@kubernetes/client-node/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", "engines": { - "node": ">=18.0.0" + "node": ">=8" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { - "node": ">=18.0.0" + "node": ">= 8" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": ">=18.0.0" + "node": ">= 8" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.214.0.tgz", + "integrity": "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@opentelemetry/auto-instrumentations-node": { + "version": "0.70.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.70.1.tgz", + "integrity": "sha512-r8BKs0rHtBAzZViPIuzSD2eh65fOPau0NqVsca2sACuZ6LFGu6a+QMhqq7skXz+/OqKwFr/7/b6VsaNMS+zZpQ==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/instrumentation-amqplib": "^0.59.0", + "@opentelemetry/instrumentation-aws-lambda": "^0.64.0", + "@opentelemetry/instrumentation-aws-sdk": "^0.67.0", + "@opentelemetry/instrumentation-bunyan": "^0.57.0", + "@opentelemetry/instrumentation-cassandra-driver": "^0.57.0", + "@opentelemetry/instrumentation-connect": "^0.55.0", + "@opentelemetry/instrumentation-cucumber": "^0.28.0", + "@opentelemetry/instrumentation-dataloader": "^0.29.0", + "@opentelemetry/instrumentation-dns": "^0.55.0", + "@opentelemetry/instrumentation-express": "^0.60.0", + "@opentelemetry/instrumentation-fastify": "^0.56.0", + "@opentelemetry/instrumentation-fs": "^0.31.0", + "@opentelemetry/instrumentation-generic-pool": "^0.55.0", + "@opentelemetry/instrumentation-graphql": "^0.60.0", + "@opentelemetry/instrumentation-grpc": "^0.212.0", + "@opentelemetry/instrumentation-hapi": "^0.58.0", + "@opentelemetry/instrumentation-http": "^0.212.0", + "@opentelemetry/instrumentation-ioredis": "^0.60.0", + "@opentelemetry/instrumentation-kafkajs": "^0.21.0", + "@opentelemetry/instrumentation-knex": "^0.56.0", + "@opentelemetry/instrumentation-koa": "^0.60.0", + "@opentelemetry/instrumentation-lru-memoizer": "^0.56.0", + "@opentelemetry/instrumentation-memcached": "^0.55.0", + "@opentelemetry/instrumentation-mongodb": "^0.65.0", + "@opentelemetry/instrumentation-mongoose": "^0.58.0", + "@opentelemetry/instrumentation-mysql": "^0.58.0", + "@opentelemetry/instrumentation-mysql2": "^0.58.0", + "@opentelemetry/instrumentation-nestjs-core": "^0.58.0", + "@opentelemetry/instrumentation-net": "^0.56.0", + "@opentelemetry/instrumentation-openai": "^0.10.0", + "@opentelemetry/instrumentation-oracledb": "^0.37.0", + "@opentelemetry/instrumentation-pg": "^0.64.0", + "@opentelemetry/instrumentation-pino": "^0.58.0", + "@opentelemetry/instrumentation-redis": "^0.60.0", + "@opentelemetry/instrumentation-restify": "^0.57.0", + "@opentelemetry/instrumentation-router": "^0.56.0", + "@opentelemetry/instrumentation-runtime-node": "^0.25.0", + "@opentelemetry/instrumentation-socket.io": "^0.59.0", + "@opentelemetry/instrumentation-tedious": "^0.31.0", + "@opentelemetry/instrumentation-undici": "^0.22.0", + "@opentelemetry/instrumentation-winston": "^0.56.0", + "@opentelemetry/resource-detector-alibaba-cloud": "^0.33.2", + "@opentelemetry/resource-detector-aws": "^2.12.0", + "@opentelemetry/resource-detector-azure": "^0.20.0", + "@opentelemetry/resource-detector-container": "^0.8.3", + "@opentelemetry/resource-detector-gcp": "^0.47.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/sdk-node": "^0.212.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.4.1", + "@opentelemetry/core": "^2.0.0" + } + }, + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/configuration": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.212.0.tgz", + "integrity": "sha512-D8sAY6RbqMa1W8lCeiaSL2eMCW2MF87QI3y+I6DQE1j+5GrDMwiKPLdzpa/2/+Zl9v1//74LmooCTCJBvWR8Iw==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/core": "2.5.1", + "yaml": "^2.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/context-async-hooks": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.1.tgz", + "integrity": "sha512-MHbu8XxCHcBn6RwvCt2Vpn1WnLMNECfNKYB14LI5XypcgH4IE0/DiVifVR9tAkwPMyLXN8dOoPJfya3IryLQVw==", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.212.0.tgz", + "integrity": "sha512-/0bk6fQG+eSFZ4L6NlckGTgUous/ib5+OVdg0x4OdwYeHzV3lTEo3it1HgnPY6UKpmX7ki+hJvxjsOql8rCeZA==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/sdk-logs": "0.212.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.212.0.tgz", + "integrity": "sha512-JidJasLwG/7M9RTxV/64xotDKmFAUSBc9SNlxI32QYuUMK5rVKhHNWMPDzC7E0pCAL3cu+FyiKvsTwLi2KqPYw==", "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" + "@opentelemetry/api-logs": "0.212.0", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/sdk-logs": "0.212.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.212.0.tgz", + "integrity": "sha512-RpKB5UVfxc7c6Ta1UaCrxXDTQ0OD7BCGT66a97Q5zR1x3+9fw4dSaiqMXT/6FAWj2HyFbem6Rcu1UzPZikGTWQ==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-logs": "0.212.0", + "@opentelemetry/sdk-trace-base": "2.5.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.212.0.tgz", + "integrity": "sha512-/6Gqf9wpBq22XsomR1i0iPGnbQtCq2Vwnrq5oiDPjYSqveBdK1jtQbhGfmpK2mLLxk4cPDtD1ZEYdIou5K8EaA==", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.212.0", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-metrics": "2.5.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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" - } - ], + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.212.0.tgz", + "integrity": "sha512-8hgBw3aTTRpSTkU4b9MLf/2YVLnfWp+hfnLq/1Fa2cky+vx6HqTodo+Zv1GTIrAKMOOwgysOjufy0gTxngqeBg==", "dependencies": { - "strnum": "^2.1.0" + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-metrics": "2.5.1" }, - "bin": { - "fxparser": "src/cli/cli.js" + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-http/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" - } - ] - }, - "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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.212.0.tgz", + "integrity": "sha512-C7I4WN+ghn3g7SnxXm2RK3/sRD0k/BYcXaK6lGU3yPjiM7a1M25MLuM6zY3PeVPPzzTZPfuS7+wgn/tHk768Xw==", "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" + "@opentelemetry/core": "2.5.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.212.0", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-metrics": "2.5.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-prometheus": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.212.0.tgz", + "integrity": "sha512-hJFLhCJba5MW5QHexZMHZdMhBfNqNItxOsN0AZojwD1W2kU9xM+BEICowFGJFo/vNV+I2BJvTtmuKafeDSAo7Q==", "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" + "@opentelemetry/core": "2.5.1", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-metrics": "2.5.1", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.212.0.tgz", + "integrity": "sha512-9xTuYWp8ClBhljDGAoa0NSsJcsxJsC9zCFKMSZJp1Osb9pjXCMRdA6fwXtlubyqe7w8FH16EWtQNKx/FWi+Ghw==", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-trace-base": "2.5.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.212.0.tgz", + "integrity": "sha512-v/0wMozNoiEPRolzC4YoPo4rAT0q8r7aqdnRw3Nu7IDN0CGFzNQazkfAlBJ6N5y0FYJkban7Aw5WnN73//6YlA==", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-trace-base": "2.5.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.212.0.tgz", + "integrity": "sha512-d1ivqPT0V+i0IVOOdzGaLqonjtlk5jYrW7ItutWzXL/Mk+PiYb59dymy/i2reot9dDnBFWfrsvxyqdutGF5Vig==", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-trace-base": "2.5.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/exporter-zipkin": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.5.1.tgz", + "integrity": "sha512-Me6JVO7WqXGXsgr4+7o+B7qwKJQbt0c8WamFnxpkR43avgG9k/niTntwCaXiXUTjonWy0+61ZuX6CGzj9nn8CQ==", "dependencies": { - "@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" + "@opentelemetry/core": "2.5.1", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-trace-base": "2.5.1", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "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" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/instrumentation-aws-sdk": { + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.67.0.tgz", + "integrity": "sha512-btpwJnZ2RBXDh/pTpfVpInpBu9Pedi+lbLKbt3naB344SggbbYnIdT7u8EzmGIApWi9EV91vw7hm896I7nESQA==", "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" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.34.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/instrumentation-fastify": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.56.0.tgz", + "integrity": "sha512-zotOPoZsWtMF47BjottK23XaaBSmVuwG5D/R3FlGfAAwMNFoDR3IY1OGO9v9KfOU/1/xDVkxsQ22NFfu9lE8aA==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/instrumentation-http": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.212.0.tgz", + "integrity": "sha512-t2nt16Uyv9irgR+tqnX96YeToOStc3X5js7Ljn3EKlI2b4Fe76VhMkTXtsTQ0aId6AsYgefrCRnXSCo/Fn/vww==", "dependencies": { - "@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" + "@opentelemetry/core": "2.5.1", + "@opentelemetry/instrumentation": "0.212.0", + "@opentelemetry/semantic-conventions": "^1.29.0", + "forwarded-parse": "2.1.2" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/instrumentation-knex": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.56.0.tgz", + "integrity": "sha512-pKqtY5lbAQ70MC5K/BJeAA1t2gAUlRBZBAJ5ergRUNs5jw8zbdOXEZOLztiuNvQqD2z4a9N0Tkde9JMFm2pKMQ==", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.212.0.tgz", + "integrity": "sha512-HoMv5pQlzbuxiMS0hN7oiUtg8RsJR5T7EhZccumIWxYfNo/f4wFc7LPDfFK6oHdG2JF/+qTocfqIHoom+7kLpw==", "dependencies": { - "@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" + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-transformer": "0.212.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.212.0.tgz", + "integrity": "sha512-YidOSlzpsun9uw0iyIWrQp6HxpMtBlECE3tiHGAsnpEqJWbAUWcMnIffvIuvTtTQ1OyRtwwaE79dWSQ8+eiB7g==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.212.0.tgz", + "integrity": "sha512-bj7zYFOg6Db7NUwsRZQ/WoVXpAf41WY2gsd3kShSfdpZQDRKHWJiRZIg7A8HvWsf97wb05rMFzPbmSHyjEl9tw==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-logs": "0.212.0", + "@opentelemetry/sdk-metrics": "2.5.1", + "@opentelemetry/sdk-trace-base": "2.5.1", + "protobufjs": "8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/propagator-b3": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.5.1.tgz", + "integrity": "sha512-AU6sZgunZrZv/LTeHP+9IQsSSH5p3PtOfDPe8VTdwYH69nZCfvvvXehhzu+9fMW2mgJMh5RVpiH8M9xuYOu5Dg==", "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.5.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/propagator-jaeger": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.5.1.tgz", + "integrity": "sha512-8+SB94/aSIOVGDUPRFSBRHVUm2A8ye1vC6/qcf/D+TF4qat7PC6rbJhRxiUGDXZtMtKEPM/glgv5cBGSJQymSg==", "dependencies": { - "@smithy/middleware-serde": "^4.0.8", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.3", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.5.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/resources": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.1.tgz", + "integrity": "sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ==", "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" + "@opentelemetry/core": "2.5.1", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/sdk-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.212.0.tgz", + "integrity": "sha512-qglb5cqTf0mOC1sDdZ7nfrPjgmAqs2OxkzOPIf2+Rqx8yKBK0pS7wRtB1xH30rqahBIut9QJDbDePyvtyqvH/Q==", "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" + "@opentelemetry/api-logs": "0.212.0", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/resources": "2.5.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.1.tgz", + "integrity": "sha512-RKMn3QKi8nE71ULUo0g/MBvq1N4icEBo7cQSKnL3URZT16/YH3nSVgWegOjwx7FRBTrjOIkMJkCUn/ZFIEfn4A==", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.5.1", + "@opentelemetry/resources": "2.5.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/sdk-node": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.212.0.tgz", + "integrity": "sha512-tJzVDk4Lo44MdgJLlP+gdYdMnjxSNsjC/IiTxj5CFSnsjzpHXwifgl3BpUX67Ty3KcdubNVfedeBc/TlqHXwwg==", + "dependencies": { + "@opentelemetry/api-logs": "0.212.0", + "@opentelemetry/configuration": "0.212.0", + "@opentelemetry/context-async-hooks": "2.5.1", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/exporter-logs-otlp-grpc": "0.212.0", + "@opentelemetry/exporter-logs-otlp-http": "0.212.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.212.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.212.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.212.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.212.0", + "@opentelemetry/exporter-prometheus": "0.212.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.212.0", + "@opentelemetry/exporter-trace-otlp-http": "0.212.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.212.0", + "@opentelemetry/exporter-zipkin": "2.5.1", + "@opentelemetry/instrumentation": "0.212.0", + "@opentelemetry/propagator-b3": "2.5.1", + "@opentelemetry/propagator-jaeger": "2.5.1", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-logs": "0.212.0", + "@opentelemetry/sdk-metrics": "2.5.1", + "@opentelemetry/sdk-trace-base": "2.5.1", + "@opentelemetry/sdk-trace-node": "2.5.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.1.tgz", + "integrity": "sha512-iZH3Gw8cxQn0gjpOjJMmKLd9GIaNh/E3v3ST67vyzLSxHBs14HsG4dy7jMYyC5WXGdBVEcM7U/XTF5hCQxjDMw==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.5.1", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.5.1.tgz", + "integrity": "sha512-9lopQ6ZoElETOEN0csgmtEV5/9C7BMfA7VtF4Jape3i954b6sTY2k3Xw3CxUTKreDck/vpAuJM+EDo4zheUw+A==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/context-async-hooks": "2.5.1", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/sdk-trace-base": "2.5.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/auto-instrumentations-node/node_modules/protobufjs": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.0.0.tgz", + "integrity": "sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw==", + "hasInstallScript": true, "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=12.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/configuration": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.213.0.tgz", + "integrity": "sha512-MfVgZiUuwL1d3bPPvXcEkVHGTGNUGoqGK97lfwBuRoKttcVGGqDyxTCCVa5MGbirtBQkUTysXMBUVWPaq7zbWw==", "dependencies": { - "@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" + "@opentelemetry/core": "2.6.0", + "yaml": "^2.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/configuration/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "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" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", - "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.6.0.tgz", + "integrity": "sha512-L8UyDwqpTcbkIK5cgwDRDYDoEhQoj8wp8BwsO19w3LB1Z41yEQm2VJyNfAi9DrLP/YTqXqWpKHyZfR9/tFYo1Q==", "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/core": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.1.tgz", + "integrity": "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.213.0.tgz", + "integrity": "sha512-QiRZzvayEOFnenSXi85Eorgy5WTqyNQ+E7gjl6P6r+W3IUIwAIH8A9/BgMWfP056LwmdrBL6+qvnwaIEmug6Yg==", "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/sdk-logs": "0.213.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "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" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.213.0.tgz", + "integrity": "sha512-vqDVSpLp09ZzcFIdb7QZrEFPxUlO3GzdhBKLstq3jhYB5ow3+ZtV5V0ngSdi/0BZs+J5WPiN1+UDV4X5zD/GzA==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.213.0", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/sdk-logs": "0.213.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/api-logs": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.213.0.tgz", + "integrity": "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.213.0.tgz", + "integrity": "sha512-gQk41nqfK3KhDk8jbSo3LR/fQBlV7f6Q5xRcfDmL1hZlbgXQPdVFV9/rIfYUrCoq1OM+2NnKnFfGjBt6QpLSsA==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.213.0", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-logs": "0.213.0", + "@opentelemetry/sdk-trace-base": "2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/api-logs": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.213.0.tgz", + "integrity": "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==", "dependencies": { - "@smithy/types": "^4.3.1" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.215.0.tgz", + "integrity": "sha512-1TAMliHQvzc+v1OtnLMHSk5sU8BSkJbxIKrWzuCWcQjajWrvem/r5ugLK6agI0PjPz/ADfZju5AVYedlNyeO9g==", + "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" + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.7.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.215.0", + "@opentelemetry/otlp-exporter-base": "0.215.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.215.0", + "@opentelemetry/otlp-transformer": "0.215.0", + "@opentelemetry/resources": "2.7.0", + "@opentelemetry/sdk-metrics": "2.7.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/api-logs": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.215.0.tgz", + "integrity": "sha512-xrFlqhdhUyO8wSRn6DjE0145/HPWSJ5Nm0C7vWua6TdL/FSEAZvEyvdsa9CRXuxo9ebb7j/NEPhEcO62IJ0qUA==", + "license": "Apache-2.0", "dependencies": { - "@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" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.0.tgz", + "integrity": "sha512-DT12SXVwV2eoJrGf4nnsvZojxxeQo+LlNAsoYGRRObPWTeN6APiqZ2+nqDCQDvQX40eLi1AePONS0onoASp3yQ==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.215.0.tgz", + "integrity": "sha512-FRydO5j7MWnXK9ghfykKxiSM8I5UeiicK/UNl3/mv86xoEKkb+LKz1I3WXgkuYVOQf22VNqbPO58s2W1mVWtEQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.7.0", + "@opentelemetry/otlp-exporter-base": "0.215.0", + "@opentelemetry/otlp-transformer": "0.215.0", + "@opentelemetry/resources": "2.7.0", + "@opentelemetry/sdk-metrics": "2.7.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.215.0.tgz", + "integrity": "sha512-lHrfbmeLSmesGSkkHiqDwOzfaEMSWXdc7q6UoLfbW8byONCb+bE/zkAr0kapN4US1baT/2nbpNT7Cn9XoB96Vg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.7.0", + "@opentelemetry/otlp-transformer": "0.215.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.215.0.tgz", + "integrity": "sha512-WkuHkUrhwNxTKrm7Xuf6S+HmLNbk2T8S2YiZhN606RfgetSQb9xLp4NizWLwXvw63uxGsBaK262dirFO2yht2g==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.7.0", + "@opentelemetry/otlp-exporter-base": "0.215.0", + "@opentelemetry/otlp-transformer": "0.215.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.215.0.tgz", + "integrity": "sha512-cWwBvaV+vkXHkSoTYR8hGw+AW03UlgTr6xtrUKOMeum3T+8vffYXIfXu6KY5MLu8O9QtoBKqaKWw9I5xoOepng==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.215.0", + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0", + "@opentelemetry/sdk-logs": "0.215.0", + "@opentelemetry/sdk-metrics": "2.7.0", + "@opentelemetry/sdk-trace-base": "2.7.0", + "protobufjs": "^8.0.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/resources": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.0.tgz", + "integrity": "sha512-K+oi0hNMv94EpZbnW3eyu2X6SGVpD3O5DhG2NIp65Hc7lhAj9brRXTAVzh3wB82+q3ThakEf7Zd7RsFUqcTc7A==", + "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.7.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/sdk-logs": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.215.0.tgz", + "integrity": "sha512-y3ucOmphzc4vgBTyIGchs+N/1rkACmoka8QalT2z1LBNM232Z17zMYayHcMl+dgMoOadZ0b72UZv7mDtqy1cFA==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.215.0", + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.0.tgz", + "integrity": "sha512-Yg9zEXJB50DLVLpsKPk7NmNqlPlS+OvqhJGh0A8oawIOTPOwlm4eXs9BMJV7L79lvEwI+dWtAj+YjTyddV336A==", + "license": "Apache-2.0", "dependencies": { - "@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" + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/protobufjs": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.0.1.tgz", + "integrity": "sha512-NWWCCscLjs+cOKF/s/XVNFRW7Yih0fdH+9brffR5NZCy8k42yRdl5KlWKMVXuI1vfCoy4o1z80XR/W/QUb3V3w==", + "hasInstallScript": true, + "license": "BSD-3-Clause", "dependencies": { - "@smithy/config-resolver": "^4.1.4", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=12.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.213.0.tgz", + "integrity": "sha512-yw3fTIw4KQIRXC/ZyYQq5gtA3Ogfdfz/g5HVgleobQAcjUUE8Nj3spGMx8iQPp+S+u6/js7BixufRkXhzLmpJA==", "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-metrics": "2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.0.tgz", + "integrity": "sha512-CicxWZxX6z35HR83jl+PLgtFgUrKRQ9LCXyxgenMnz5A1lgYWfAog7VtdOvGkJYyQgMNPhXQwkYrDLujk7z1Iw==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.213.0.tgz", + "integrity": "sha512-geHF+zZaDb0/WRkJTxR8o8dG4fCWT/Wq7HBdNZCxwH5mxhwRi/5f37IDYH7nvU+dwU6IeY4Pg8TPI435JCiNkg==", "dependencies": { - "@smithy/service-error-classification": "^4.0.6", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.213.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-metrics": "2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "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" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.0.tgz", + "integrity": "sha512-CicxWZxX6z35HR83jl+PLgtFgUrKRQ9LCXyxgenMnz5A1lgYWfAog7VtdOvGkJYyQgMNPhXQwkYrDLujk7z1Iw==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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==", + "node_modules/@opentelemetry/exporter-prometheus": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.213.0.tgz", + "integrity": "sha512-FyV3/JfKGAgx+zJUwCHdjQHbs+YeGd2fOWvBHYrW6dmfv/w89lb8WhJTSZEoWgP525jwv/gFeBttlGu1flebdA==", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-metrics": "2.6.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/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" - } - ], - "dependencies": { - "strnum": "^2.1.0" + "node": "^18.19.0 || >=20.6.0" }, - "bin": { - "fxparser": "src/cli/cli.js" + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/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" - } - ] - }, - "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==", + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "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" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-node/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==", + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.0.tgz", + "integrity": "sha512-CicxWZxX6z35HR83jl+PLgtFgUrKRQ9LCXyxgenMnz5A1lgYWfAog7VtdOvGkJYyQgMNPhXQwkYrDLujk7z1Iw==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-node/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==", + "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.213.0.tgz", + "integrity": "sha512-L8y6piP4jBIIx1Nv7/9hkx25ql6/Cro/kQrs+f9e8bPF0Ar5Dm991v7PnbtubKz6Q4fT872H56QXUWVnz/Cs4Q==", "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" + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-trace-base": "2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-node/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==", + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-node/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==", + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.213.0.tgz", + "integrity": "sha512-tnRmJD39aWrE/Sp7F6AbRNAjKHToDkAqBi6i0lESpGWz3G+f4bhVAV6mgSXH2o18lrDVJXo6jf9bAywQw43wRA==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-trace-base": "2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-node/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==", + "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-node/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==", + "node_modules/@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.213.0.tgz", + "integrity": "sha512-six3vPq3sL+ge1iZOfKEg+RHuFQhGb8ZTdlvD234w/0gi8ty/qKD46qoGpKvM3amy5yYunWBKiFBW47WaVS26w==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-trace-base": "2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-node/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==", + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-node/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==", + "node_modules/@opentelemetry/exporter-zipkin": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.6.0.tgz", + "integrity": "sha512-AFP77OQMLfw/Jzh6WT2PtrywstNjdoyT9t9lYrYdk1s4igsvnMZ8DkZKCwxsItC01D+4Lydgrb+Wy0bAvpp8xg==", "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-trace-base": "2.6.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" } }, - "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==", + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "@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" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/host-metrics": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@opentelemetry/host-metrics/-/host-metrics-0.38.3.tgz", + "integrity": "sha512-8iSOA8VPGoB5p/RIC8n/dcSe4cluCEWoznWENZfXR8sWQOQvergFu7v798xp7S5WQlZo1zfn1nVXx8dbyQ9m6Q==", "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" + "systeminformation": "^5.31.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.214.0.tgz", + "integrity": "sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.214.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-amqplib": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.59.0.tgz", + "integrity": "sha512-xscSgOJA+GHphESDZxBHNk/zjNaEgoeufMwmiqYdL+qM27Xw3BbR9vN6Ucbq9dW6Y+oYUPgTTj17qf+Za4+uzg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-amqplib/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", + "node_modules/@opentelemetry/instrumentation-amqplib/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@smithy/middleware-serde": "^4.0.8", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.3", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-aws-lambda": { + "version": "0.64.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.64.0.tgz", + "integrity": "sha512-vYhM/a8fG34/Dl/Q9gfv5Ih3OFPgqeyn79S8FN+Xs/QZw6h6L8a1lDa3CyigyicOXLCmVIM7Fc9vFD4BGqgGLA==", "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" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/aws-lambda": "^8.10.155" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-aws-lambda/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-aws-lambda/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@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" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-aws-sdk": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.59.0.tgz", + "integrity": "sha512-GN/9YGBMb//s0vnchM2jMCkCaIKDB/Piau72fcuqcDXNBffMgu+AA9vCHZD2umriciXLtXJ2GXTh2/yaaHwLIw==", "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.204.0", + "@opentelemetry/semantic-conventions": "^1.34.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-aws-sdk/node_modules/@opentelemetry/api-logs": { + "version": "0.204.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.204.0.tgz", + "integrity": "sha512-DqxY8yoAaiBPivoJD4UtgrMS8gEmzZ5lnaxzPojzLVHBGqPxgWm4zcuvcUHZiqQ6kRX2Klel2r9y8cA2HAtqpw==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-aws-sdk/node_modules/@opentelemetry/instrumentation": { + "version": "0.204.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.204.0.tgz", + "integrity": "sha512-vV5+WSxktzoMP8JoYWKeopChy6G3HKk4UQ2hESCRDUUTZqQ3+nM3u8noVG0LmNfRWwcFBnbZ71GKC7vaYYdJ1g==", "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.204.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-aws-sdk/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "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" + "ms": "^2.1.3" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process/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==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "node": ">=6.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-aws-sdk/node_modules/import-in-the-middle": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz", + "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-aws-sdk/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/@opentelemetry/instrumentation-aws-sdk/node_modules/require-in-the-middle": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", + "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.6.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-bunyan": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.57.0.tgz", + "integrity": "sha512-W4zLz1Y9ptCsdL+QMXR7xQaBHkJivLBmVlLCjUe23rX4V8E65fGAtlIJSKTKAfz4aEgtWgQAGMdkeqACwG0Caw==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "^0.212.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@types/bunyan": "1.8.11" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-bunyan/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-bunyan/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "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" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-cassandra-driver": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.57.0.tgz", + "integrity": "sha512-xLwrK+XnN32IB5i6t/a2j+SVdjlq/BIgjpVRkke4HAsKjoSMy1GeSI+ZOiJffRLFb4MojcvH4RG2+nEg1uC6Zg==", "dependencies": { - "@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" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.37.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-cassandra-driver/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-cassandra-driver/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-connect": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.55.0.tgz", + "integrity": "sha512-UfGw7ubKKZBoTRjxi5KlfeECEaXZinS20RdRNlZE5tVF+O17hJOnrcGwAoQAHp6eYmxI2jW9IQ4t6450gnNF9g==", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/connect": "3.4.38" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-connect/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-connect/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-cucumber": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.28.0.tgz", + "integrity": "sha512-kim+bRxu4LZqKEyF2SgO01tgG88W+/iYltyP1XjT31FIXzlBjzQpwtSLLM8byayO85mcZIBha54WSNFDLM/7qQ==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-cucumber/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-cucumber/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "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" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-dataloader": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.29.0.tgz", + "integrity": "sha512-220WjRb1G1UiAKbVblSMxwxxFdpyB4wj1XYIO9BJs5r62Azj2dL5fyZiXK3/WO6wB3uLul9R946iKI1bpPxktQ==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/instrumentation": "^0.212.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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==", + "node_modules/@opentelemetry/instrumentation-dataloader/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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" - } - ], + "node_modules/@opentelemetry/instrumentation-dataloader/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "strnum": "^2.1.0" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, - "bin": { - "fxparser": "src/cli/cli.js" + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-process/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" - } - ] - }, - "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==", + "node_modules/@opentelemetry/instrumentation-dns": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.55.0.tgz", + "integrity": "sha512-cfWLaFi22V+sQrKY7t6QroYzT3kO9m3PpkN1OXYmuCyfwxQaXOVlF8NSAHtua/RQYw0aQl+2fe6JOWyJdEZiwA==", "dependencies": { - "@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" + "@opentelemetry/instrumentation": "^0.212.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-dns/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "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" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-dns/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-express": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.60.0.tgz", + "integrity": "sha512-KghHCDqKq0D7iuPIVCuPSXut5WVAI6uwKcPrhwTUJL5VE2LC18id2vKoiAm1V8XvVlgIGAiECtEvbrFwkTCj3g==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-express/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", + "node_modules/@opentelemetry/instrumentation-express/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@smithy/middleware-serde": "^4.0.8", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.3", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-fs": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.31.0.tgz", + "integrity": "sha512-C7tdXGDnkMgLVlE79VSekB+Y+P345zKUigvFMs5M7U0GIYA8ERx3FS0aAcY/ICIq9YwRmH2uuMb++Br5M2vNUg==", "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" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-fs/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-fs/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@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" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-generic-pool": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.55.0.tgz", + "integrity": "sha512-7hWiyLbEX/dIS4LZy/h8VaAQPs8oBeEqsrysDWbos0b9PF414L6Rsbi2um/omtxIs+GTvsbuqDscWigeaxyWdA==", "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/instrumentation": "^0.212.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-generic-pool/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-generic-pool/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-graphql": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.60.0.tgz", + "integrity": "sha512-XPATrmxAd2tFCsYbJ3eVIXt+gyvMKjc36QQuQxjtssMnAbw006Le9b5lKs7WXik7ItOpM1exATi1aDdOcCjRRg==", "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" + "@opentelemetry/instrumentation": "^0.212.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-graphql/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-graphql/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-grpc": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.212.0.tgz", + "integrity": "sha512-r1t7LNKWVhSQMUrBdDJtooFmmLZ93kGuFixqeXPoUP8W+chJCxhey9l0c0+L3xriNdyB7TzvkKHhPXUDevgVEA==", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/instrumentation": "0.212.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-grpc/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-grpc/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-hapi": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.58.0.tgz", + "integrity": "sha512-reuRApR2KHm2VsfyDgsrLhNE+IOy4uIU6n3oMjUleReHacEEZmf4vXxdt4/qcmJ6GoUXnRN2AOu3s5N3pMrgYA==", "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" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-hapi/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@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" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-hapi/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" - } + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-http": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.213.0.tgz", + "integrity": "sha512-B978Xsm5XEPGhm1P07grDoaOFLHapJPkOG9h016cJsyWWxmiLnPu2M/4Nrm7UCkHSiLnkXgC+zVGUAIahy8EEA==", "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/instrumentation": "0.213.0", + "@opentelemetry/semantic-conventions": "^1.29.0", + "forwarded-parse": "2.1.2" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/api-logs": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.213.0.tgz", + "integrity": "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/instrumentation": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.213.0.tgz", + "integrity": "sha512-3i9NdkET/KvQomeh7UaR/F4r9P25Rx6ooALlWXPIjypcEOUxksCmVu0zA70NBJWlrMW1rPr/LRidFAflLI+s/w==", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.213.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-http/node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==" + }, + "node_modules/@opentelemetry/instrumentation-http/node_modules/import-in-the-middle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.0.tgz", + "integrity": "sha512-OnGy+eYT7wVejH2XWgLRgbmzujhhVIATQH0ztIeRilwHBjTeG3pD+XnH3PKX0r9gJ0BuJmJ68q/oh9qgXnNDQg==", "dependencies": { - "tslib": "^2.6.2" + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-ioredis": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.60.0.tgz", + "integrity": "sha512-R+nnbPD9l2ruzu248qM3YDWzpdmWVaFFFv08lQqsc0EP4pT/B1GGUg06/tHOSo3L5njB2eejwyzpkvJkjaQEMA==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/redis-common": "^0.38.2", + "@opentelemetry/semantic-conventions": "^1.33.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-ioredis/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "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" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-ioredis/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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==", + "node_modules/@opentelemetry/instrumentation-kafkajs": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.21.0.tgz", + "integrity": "sha512-lkLrILnKGO7SHw1xPJnuGx2S4XwbKmQiJyzUGuEImRoU/6Gj0Nka0lkbeRd4ANN20dxr/mLdXIsUsk6DzTrX6A==", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.30.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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" - } - ], + "node_modules/@opentelemetry/instrumentation-kafkajs/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "strnum": "^2.1.0" + "@opentelemetry/api": "^1.3.0" }, - "bin": { - "fxparser": "src/cli/cli.js" + "engines": { + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/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" - } - ] - }, - "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==", + "node_modules/@opentelemetry/instrumentation-kafkajs/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@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" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-knex": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.58.0.tgz", + "integrity": "sha512-Hc/o8fSsaWxZ8r1Yw4rNDLwTpUopTf4X32y4W6UhlHmW8Wizz8wfhgOKIelSeqFVTKBBPIDUOsQWuIMxBmu8Bw==", "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" + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.33.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-koa": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.60.0.tgz", + "integrity": "sha512-UOmu2y2LHgPzKsm9xd0sCQJimr11YP4MKFc190Do1ufd8qds7Zd5BI3f6TudqYhH9dUIhojsQyUaS6K4nv5FsQ==", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.36.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-koa/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-koa/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-lru-memoizer": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.56.0.tgz", + "integrity": "sha512-vXtOValhKRgWA9tLAiTU3P37Q31OveRuM2N5iLSVHl4GzkMBQ5p50A9kSKvt5gReL6BzFDXPCM9ItJiAhSS2KQ==", "dependencies": { - "@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" + "@opentelemetry/instrumentation": "^0.212.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-lru-memoizer/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "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" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-lru-memoizer/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "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" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-memcached": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.55.0.tgz", + "integrity": "sha512-kdhW/j5X+vNCAvHVc50PZfvE7diUScg1ZkBaNFRygY3Z6IUjgPLR0luWQMDPSFun6AVo1HaMDPxbUqJrot6qrA==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/memcached": "^2.2.6" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-memcached/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@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" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-memcached/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-mongodb": { + "version": "0.65.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.65.0.tgz", + "integrity": "sha512-hOAJRs5vrY7fZolSYUXmf29Y+HFDHWrek0DeLq82uwMPjPSda7h6oumQnqEX5olzw357q/QG39/uJdkclJ/JUg==", "dependencies": { - "@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" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-mongodb/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-mongodb/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-mongoose": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.58.0.tgz", + "integrity": "sha512-3L0Fqo1y2oreISFPWaqdt/bg3NhLgrkn5U/E/9RNG1QaM81drTMBCHseMY1q8SlejjE43ZWOy+0KbmRBlUPJ+g==", "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", + "node_modules/@opentelemetry/instrumentation-mongoose/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/middleware-serde": "^4.0.8", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.3", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-mongoose/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "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" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-mysql": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.58.0.tgz", + "integrity": "sha512-wZDrBCL3WfJclV6KywWVV3/B2ZiUYmDQdgyu3pq4jK/5qSfoDmezHzT/Nayln5MVVWMAGXIMLrCj8BKa6jaKQQ==", "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" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/mysql": "2.15.27" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-mysql/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-mysql/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-mysql2": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.58.0.tgz", + "integrity": "sha512-EubjV1XZb7XHrENqF7TW2lnah+KN0LddMneKNAB8PjGVKL5lJkVV/vhJ6EIcUNn9nCWmAwZ3GRcFVEDKCnyXfQ==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@opentelemetry/sql-common": "^0.41.2" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-mysql2/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-mysql2/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@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" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-nestjs-core": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.58.0.tgz", + "integrity": "sha512-0lE9oW8j6nmvBHJoOxIQgKzMQQYNfX1nhiWZdXD0sNAMFsWBtvECWS7NAPSroKrEP53I04TcHCyyhcK4I9voXg==", "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" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.30.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-nestjs-core/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-nestjs-core/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-net": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.56.0.tgz", + "integrity": "sha512-h69x7U6f86mP3gGWWTaMkQZk0K3tBvpVMIU7E0q2kkVw6eZ5TqFm9rkaEy38moQmixiDFQ9j/2/cwxG9P7ZEeA==", "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-net/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "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" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-net/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-openai": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-openai/-/instrumentation-openai-0.10.0.tgz", + "integrity": "sha512-0lV2zxge2mMaruVCw/bmypWVu+aJ76rc0HBvAVFCPUI3zzJdgBZJZafGIHZ1IB2F6VvrDFL+JstEnle6V8brvA==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "^0.212.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.36.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-openai/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-openai/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-oracledb": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-oracledb/-/instrumentation-oracledb-0.37.0.tgz", + "integrity": "sha512-OzMghtAEAEkXlkUrZI4QcXSZq0MILeU6WC0/N5+1MSkuIkruIeaRw99/RtyS2of8vlPDa8XbbXl32Q1RM3wSyg==", "dependencies": { - "@smithy/types": "^4.3.1" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.34.0", + "@types/oracledb": "6.5.2" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-oracledb/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-oracledb/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "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" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-pg": { + "version": "0.64.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.64.0.tgz", + "integrity": "sha512-NbfB/rlfsRI3zpTjnbvJv3qwuoGLsN8FxR/XoI+ZTn1Rs62x1IenO+TSSvk4NO+7FlXpd2MiOe8LT/oNbydHGA==", "dependencies": { - "@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" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.34.0", + "@opentelemetry/sql-common": "^0.41.2", + "@types/pg": "8.15.6", + "@types/pg-pool": "2.0.7" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-pg/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-pg/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-pino": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.58.0.tgz", + "integrity": "sha512-rgy+tA7cDjuSq6dXAO40OiYP25azIDHMBtxG3RzSmCBVEYdjggl6btyuLVasX6VkOOhP2gf6PBuLMNxVwaIqAw==", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "^0.212.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-pino/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-pino/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-redis": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.60.0.tgz", + "integrity": "sha512-Ea/GffmmzIVHc9geaMjT94IR7poVZzIv4Kk/Lw0tbxGD3cBYcMUsLFVajKxpZsE1NRCECFpidAWeifCIKD0inw==", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/redis-common": "^0.38.2", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-redis/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-redis/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "@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" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-restify": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.57.0.tgz", + "integrity": "sha512-kO6MsZFU+RdXOKhsKw8SOSBYGYCdFSlza+mpBQRl1DQmveZcnidchv4V5JQPtNgHxCGH+1n3hDpLdxdGUbJPNA==", "dependencies": { - "@smithy/config-resolver": "^4.1.4", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-restify/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-restify/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-router": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.56.0.tgz", + "integrity": "sha512-PHECDGQElLazI/QbHU16C5m9fDC7DGJk+jLIwO5ca6bcp7bXhUPPUTT78l7da2pDsrz4mhv5ytYNZmBbW/Q3rA==", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-router/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/service-error-classification": "^4.0.6", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-router/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "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" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-runtime-node": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-runtime-node/-/instrumentation-runtime-node-0.25.0.tgz", + "integrity": "sha512-XaCmwBSui5KeTn8M6OzaEn1rEsNWtUkjuc1ylg0tqQTLHibNQ0n7f8v4zdF6x/nBV1OnsiYlN8RLHauGemv/TA==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/instrumentation": "^0.212.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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==", + "node_modules/@opentelemetry/instrumentation-runtime-node/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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" - } - ], + "node_modules/@opentelemetry/instrumentation-runtime-node/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "strnum": "^2.1.0" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, - "bin": { - "fxparser": "src/cli/cli.js" + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/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" - } - ] - }, - "node_modules/@aws-sdk/lib-storage": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.654.0.tgz", - "integrity": "sha512-x3o11PnghWkIaC19/TYuyc0/o6jA6Oh4sa5ZPvszaaJ+NRCrN/XXrX1XlJv720X+99WN7tdz4oEcmtgQ0RaJIQ==", + "node_modules/@opentelemetry/instrumentation-socket.io": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.59.0.tgz", + "integrity": "sha512-71DnM/FEqH0PjvU2uZvzWJeaGyVIy3rJKk8rZrxg/aS2QT3qLGb+UPL/B+1vOw4pzDPn4papLTSMpLVF9G8uvw==", "dependencies": { - "@smithy/abort-controller": "^3.1.4", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/smithy-client": "^3.3.2", - "buffer": "5.6.0", - "events": "3.3.0", - "stream-browserify": "3.0.0", - "tslib": "^2.6.2" + "@opentelemetry/instrumentation": "^0.212.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@aws-sdk/client-s3": "^3.654.0" + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/lib-storage/node_modules/@smithy/abort-controller": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.4.tgz", - "integrity": "sha512-VupaALAQlXViW3/enTf/f5l5JZYSAxoJL7f0nanhNNKnww6DGCg1oYIuNP78KDugnkwthBO6iEcym16HhWV8RQ==", + "node_modules/@opentelemetry/instrumentation-socket.io/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/lib-storage/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@opentelemetry/instrumentation-socket.io/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.654.0.tgz", - "integrity": "sha512-/lWkyeLESiK+rAB4+NCw1cVPle9RN7RW/v7B4b8ORiCn1FwZLUPmEiZSYzyh4in5oa3Mri+W/g+KafZDH6LCbA==", + "node_modules/@opentelemetry/instrumentation-tedious": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.31.0.tgz", + "integrity": "sha512-HoF2EtcyP3JR4R3jLPHohZ9lFcj1QLJyGmFfLKDTvUUjPiFuK4XZ6L1OV9HhaqvN0xY+tWKfNdCPS3r33rd0Xw==", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-arn-parser": "3.568.0", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", - "@smithy/util-config-provider": "^3.0.0", - "tslib": "^2.6.2" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/tedious": "^4.0.14" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", + "node_modules/@opentelemetry/instrumentation-tedious/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@opentelemetry/instrumentation-tedious/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.654.0.tgz", - "integrity": "sha512-S7fSlo8vdjkQTy9DmdF54ZsPwc+aA4z5Y9JVqAlGL9QiZe/fPtRE3GZ8BBbMICjBfMEa12tWjzhDz9su2c6PIA==", + "node_modules/@opentelemetry/instrumentation-undici": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.22.0.tgz", + "integrity": "sha512-yb6vEWUPOrD5i7yR1XceEEqiVHbMgr5YnUPnom5eQVCjvrTkEVswyrf9i+vvJR+28wrNqILIIphWgOOx6BjnTQ==", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.24.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.7.0" } }, - "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", + "node_modules/@opentelemetry/instrumentation-undici/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@opentelemetry/instrumentation-undici/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.654.0.tgz", - "integrity": "sha512-ZSRC+Lf9WxyoDLuTkd7JrFRrBLPLXcTOZzX6tDsnHc6tgdneBNwV3/ZOYUwQ8bdwLLnzSaQUU+X5B2BkEFKIhQ==", + "node_modules/@opentelemetry/instrumentation-winston": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.56.0.tgz", + "integrity": "sha512-ITIA0Qe61CQ6FQU/bN23pNBvJ+5U0ofoASMOOYrODtXyV9wI267AigNTTwDmv2Myt8dPEFvvVFJZKhiZLIpehA==", "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@aws-crypto/crc32c": "5.2.0", - "@aws-sdk/types": "3.654.0", - "@smithy/is-array-buffer": "^3.0.0", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "^0.212.0", + "@opentelemetry/instrumentation": "^0.212.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", + "node_modules/@opentelemetry/instrumentation-winston/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", "dependencies": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@opentelemetry/instrumentation-winston/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.654.0.tgz", - "integrity": "sha512-rxGgVHWKp8U2ubMv+t+vlIk7QYUaRCHaVpmUlJv0Wv6Q0KeO9a42T9FxHphjOTlCGQOLcjCreL9CF8Qhtb4mdQ==", + "node_modules/@opentelemetry/instrumentation/node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==" + }, + "node_modules/@opentelemetry/instrumentation/node_modules/import-in-the-middle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.0.tgz", + "integrity": "sha512-OnGy+eYT7wVejH2XWgLRgbmzujhhVIATQH0ztIeRilwHBjTeG3pD+XnH3PKX0r9gJ0BuJmJ68q/oh9qgXnNDQg==", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" }, "engines": { - "node": ">=16.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/middleware-host-header/node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.213.0.tgz", + "integrity": "sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg==", "dependencies": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-transformer": "0.213.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/middleware-host-header/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.654.0.tgz", - "integrity": "sha512-Duvv5c4DEQ7P6c0YlcvEUW3xCJi6X2uktafNGjILhVDMQwShSF/aFqNv/ikWU/luQcmWHZ9DtDjTR9UKLh6eTA==", + "node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.213.0.tgz", + "integrity": "sha512-XgRGuLE9usFNlnw2lgMIM4HTwpcIyjdU/xPoJ8v3LbBLBfjaDkIugjc9HoWa7ZSJ/9Bhzgvm/aD0bGdYUFgnTw==", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/middleware-location-constraint/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.654.0.tgz", - "integrity": "sha512-OQYb+nWlmASyXfRb989pwkJ9EVUMP1CrKn2eyTk3usl20JZmKo2Vjis6I0tLUkMSxMhnBJJlQKyWkRpD/u1FVg==", + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.213.0.tgz", + "integrity": "sha512-RSuAlxFFPjeK4d5Y6ps8L2WhaQI6CXWllIjvo5nkAlBpmq2XdYWEBGiAbOF4nDs8CX4QblJDv5BbMUft3sEfDw==", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.213.0", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-logs": "0.213.0", + "@opentelemetry/sdk-metrics": "2.6.0", + "@opentelemetry/sdk-trace-base": "2.6.0", + "protobufjs": "^7.0.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/middleware-logger/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/api-logs": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.213.0.tgz", + "integrity": "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.654.0.tgz", - "integrity": "sha512-gKSomgltKVmsT8sC6W7CrADZ4GHwX9epk3GcH6QhebVO3LA9LRbkL3TwOPUXakxxOLLUTYdOZLIOtFf7iH00lg==", + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.0.tgz", + "integrity": "sha512-CicxWZxX6z35HR83jl+PLgtFgUrKRQ9LCXyxgenMnz5A1lgYWfAog7VtdOvGkJYyQgMNPhXQwkYrDLujk7z1Iw==", "dependencies": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, - "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@opentelemetry/propagator-b3": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.6.0.tgz", + "integrity": "sha512-SguK4jMmRvQ0c0dxAMl6K+Eu1+01X0OP7RLiIuHFjOS8hlB23ZYNnhnbAdSQEh5xVXQmH0OAS0TnmVI+6vB2Kg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.654.0.tgz", - "integrity": "sha512-6prq+GK6hLMAbxEb83tBMb1YiTWWK196fJhFO/7gE5TUPL1v756RhQZzKV/njbwB1fIBjRBTuhYLh5Bn98HhdA==", - "dependencies": { - "@aws-sdk/core": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-arn-parser": "3.568.0", - "@smithy/core": "^2.4.3", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.3", - "@smithy/signature-v4": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-stream": "^3.1.6", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" + "node": "^18.19.0 || >=20.6.0" }, - "engines": { - "node": ">=16.0.0" + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", + "node_modules/@opentelemetry/propagator-b3/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@opentelemetry/propagator-jaeger": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.6.0.tgz", + "integrity": "sha512-KGWJuvp9X8X36bhHgIhWEnHAzXDInFr+Fvo9IQhhuu6pXLT8mF7HzFyx/X+auZUITvPaZhM39Phj3vK12MbhwA==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.654.0.tgz", - "integrity": "sha512-k7hkQDJh4hcRJC7YojQ11kc37SY4foryen26Eafj5qYjeG2OGMW0oZTJDl1TVFJ7AcCjqIuMIo0Ho2US/2JspQ==", + "node_modules/@opentelemetry/propagator-jaeger/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/middleware-ssec/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@opentelemetry/redis-common": { + "version": "0.38.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz", + "integrity": "sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==", "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" } }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.654.0.tgz", - "integrity": "sha512-liCcqPAyRsr53cy2tYu4qeH4MMN0eh9g6k56XzI5xd4SghXH5YWh4qOYAlQ8T66ZV4nPMtD8GLtLXGzsH8moFg==", + "node_modules/@opentelemetry/resource-detector-alibaba-cloud": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.33.2.tgz", + "integrity": "sha512-EaS54zwYmOg9Ttc79juaktpCBYqyh2IquXl534sLls+c1/pc8LZfWPMqytFt+iBvSPQ6ajraUnvi6cun4AhSjQ==", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-endpoints": "3.654.0", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", + "node_modules/@opentelemetry/resource-detector-aws": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-2.12.0.tgz", + "integrity": "sha512-VelueKblsnQEiBVqEYcvM9VEb+B8zN6nftltdO9HAD7qi/OlicP4z/UGJ9EeW2m++WabdMoj0G3QVL8YV0P9tw==", "dependencies": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@opentelemetry/resource-detector-azure": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-azure/-/resource-detector-azure-0.20.0.tgz", + "integrity": "sha512-iRy+O2cB6DOlQ/OONaK+L8Cp8nLS89dZVRp6KgnFAfzykXuq9Ws/ygJKcU3CCmjkgY5j2Vk3uVTre/E35bWhYg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/semantic-conventions": "^1.37.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.787.0.tgz", - "integrity": "sha512-xk03q1xpKNHgbuo+trEf1dFrI239kuMmjKKsqLEsHlAZbuFq4yRGMlHBrVMnKYOPBhVFDS/VineM991XI52fKg==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/resource-detector-container": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.8.3.tgz", + "integrity": "sha512-5J0JP2cy655rBKM9Doz26ffO3rG+Xqm7OXeNXkckzmc3JmL6Bj3dPBKugPYsfemhEIqtf7INH9UmPQqTMuWoHg==", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.775.0", - "@aws-sdk/middleware-host-header": "3.775.0", - "@aws-sdk/middleware-logger": "3.775.0", - "@aws-sdk/middleware-recursion-detection": "3.775.0", - "@aws-sdk/middleware-user-agent": "3.787.0", - "@aws-sdk/region-config-resolver": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@aws-sdk/util-endpoints": "3.787.0", - "@aws-sdk/util-user-agent-browser": "3.775.0", - "@aws-sdk/util-user-agent-node": "3.787.0", - "@smithy/config-resolver": "^4.1.0", - "@smithy/core": "^3.2.0", - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/hash-node": "^4.0.2", - "@smithy/invalid-dependency": "^4.0.2", - "@smithy/middleware-content-length": "^4.0.2", - "@smithy/middleware-endpoint": "^4.1.0", - "@smithy/middleware-retry": "^4.1.0", - "@smithy/middleware-serde": "^4.0.3", - "@smithy/middleware-stack": "^4.0.2", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/node-http-handler": "^4.0.4", - "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "@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.8", - "@smithy/util-defaults-mode-node": "^4.0.8", - "@smithy/util-endpoints": "^3.0.2", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-retry": "^4.0.2", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/core": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.775.0.tgz", - "integrity": "sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/resource-detector-gcp": { + "version": "0.47.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.47.0.tgz", + "integrity": "sha512-57T/kRVdU0ch1P4KPEkmU2b5mWNlUs8hHgqrBYVF+fNZMc1jMdL1mANZhEzoLtWKIeoCEy+57Itt7RkXAYNJiQ==", "dependencies": { - "@aws-sdk/types": "3.775.0", - "@smithy/core": "^3.2.0", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/signature-v4": "^5.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/util-middleware": "^4.0.2", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0", + "gcp-metadata": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.775.0.tgz", - "integrity": "sha512-tkSegM0Z6WMXpLB8oPys/d+umYIocvO298mGvcMCncpRl77L9XkvSLJIFzaHes+o7djAgIduYw8wKIMStFss2w==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/resources": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.0.tgz", + "integrity": "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ==", "dependencies": { - "@aws-sdk/types": "3.775.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-logger": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.775.0.tgz", - "integrity": "sha512-FaxO1xom4MAoUJsldmR92nT1G6uZxTdNYOFYtdHfd6N2wcNaTuxgjIvqzg5y7QIH9kn58XX/dzf1iTjgqUStZw==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "@aws-sdk/types": "3.775.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.775.0.tgz", - "integrity": "sha512-GLCzC8D0A0YDG5u3F5U03Vb9j5tcOEFhr8oc6PDk0k0vm5VwtZOE6LvK7hcCSoAB4HXyOUM0sQuXrbaAh9OwXA==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.213.0.tgz", + "integrity": "sha512-00xlU3GZXo3kXKve4DLdrAL0NAFUaZ9appU/mn00S/5kSUdAvyYsORaDUfR04Mp2CLagAOhrzfUvYozY/EZX2g==", "dependencies": { - "@aws-sdk/types": "3.775.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.213.0", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.787.0.tgz", - "integrity": "sha512-Lnfj8SmPLYtrDFthNIaNj66zZsBCam+E4XiUDr55DIHTGstH6qZ/q6vg0GfbukxwSmUcGMwSR4Qbn8rb8yd77g==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/api-logs": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.213.0.tgz", + "integrity": "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==", "dependencies": { - "@aws-sdk/core": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@aws-sdk/util-endpoints": "3.787.0", - "@smithy/core": "^3.2.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.775.0.tgz", - "integrity": "sha512-40iH3LJjrQS3LKUJAl7Wj0bln7RFPEvUYKFxtP8a+oKFDO0F65F52xZxIJbPn6sHkxWDAnZlGgdjZXM3p2g5wQ==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "@aws-sdk/types": "3.775.0", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/types": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.775.0.tgz", - "integrity": "sha512-ZoGKwa4C9fC9Av6bdfqcW6Ix5ot05F/S4VxWR2nHuMv7hzfmAjTOcUiWT7UR4hM/U0whf84VhDtXN/DWAk52KA==", + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.0.tgz", + "integrity": "sha512-Vd7h95av/LYRsAVN7wbprvvJnHkq7swMXAo7Uad0Uxf9jl6NSReLa0JNivrcc5BVIx/vl2t+cgdVQQbnVhsR9w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.787.0.tgz", - "integrity": "sha512-fd3zkiOkwnbdbN0Xp9TsP5SWrmv0SpT70YEdbb8wAj2DWQwiCmFszaSs+YCvhoCdmlR3Wl9Spu0pGpSAGKeYvQ==", + "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/core": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.0.tgz", + "integrity": "sha512-DT12SXVwV2eoJrGf4nnsvZojxxeQo+LlNAsoYGRRObPWTeN6APiqZ2+nqDCQDvQX40eLi1AePONS0onoASp3yQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.775.0", - "@smithy/types": "^4.2.0", - "@smithy/util-endpoints": "^3.0.2", - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.775.0.tgz", - "integrity": "sha512-txw2wkiJmZKVdDbscK7VBK+u+TJnRtlUjRTLei+elZg2ADhpQxfVAQl436FUeIv6AhB/oRHW6/K/EAGXUSWi0A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.775.0", - "@smithy/types": "^4.2.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.787.0.tgz", - "integrity": "sha512-mG7Lz8ydfG4SF9e8WSXiPQ/Lsn3n8A5B5jtPROidafi06I3ckV2WxyMLdwG14m919NoS6IOfWHyRGSqWIwbVKA==", + "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/resources": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.0.tgz", + "integrity": "sha512-K+oi0hNMv94EpZbnW3eyu2X6SGVpD3O5DhG2NIp65Hc7lhAj9brRXTAVzh3wB82+q3ThakEf7Zd7RsFUqcTc7A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.787.0", - "@aws-sdk/types": "3.775.0", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.7.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "aws-crt": ">=1.0.0" + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.213.0.tgz", + "integrity": "sha512-8s7SQtY8DIAjraXFrUf0+I90SBAUQbsMWMtUGKmusswRHWXtKJx42aJQMoxEtC82Csqj+IlBH6FoP8XmmUDSrQ==", + "dependencies": { + "@opentelemetry/api-logs": "0.213.0", + "@opentelemetry/configuration": "0.213.0", + "@opentelemetry/context-async-hooks": "2.6.0", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/exporter-logs-otlp-grpc": "0.213.0", + "@opentelemetry/exporter-logs-otlp-http": "0.213.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.213.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.213.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.213.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.213.0", + "@opentelemetry/exporter-prometheus": "0.213.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.213.0", + "@opentelemetry/exporter-trace-otlp-http": "0.213.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.213.0", + "@opentelemetry/exporter-zipkin": "2.6.0", + "@opentelemetry/instrumentation": "0.213.0", + "@opentelemetry/propagator-b3": "2.6.0", + "@opentelemetry/propagator-jaeger": "2.6.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-logs": "0.213.0", + "@opentelemetry/sdk-metrics": "2.6.0", + "@opentelemetry/sdk-trace-base": "2.6.0", + "@opentelemetry/sdk-trace-node": "2.6.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/abort-controller": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.2.tgz", - "integrity": "sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/api-logs": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.213.0.tgz", + "integrity": "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==", "dependencies": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/config-resolver": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.0.tgz", - "integrity": "sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/core": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.2.0.tgz", - "integrity": "sha512-k17bgQhVZ7YmUvA8at4af1TDpl0NDMBuBKJl8Yg0nrefwmValU+CnA5l/AriVdQNthU/33H3nK71HrLgqOPr1Q==", + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.213.0.tgz", + "integrity": "sha512-Z8gYKUAU48qwm+a1tjnGv9xbE7a5lukVIwgF6Z5i3VPXPVMe4Sjra0nN3zU7m277h+V+ZpsPGZJ2Xf0OTkL7/w==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.0.3", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-stream": "^4.2.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.213.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-metrics": "2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/credential-provider-imds": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.2.tgz", - "integrity": "sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/instrumentation": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.213.0.tgz", + "integrity": "sha512-3i9NdkET/KvQomeh7UaR/F4r9P25Rx6ooALlWXPIjypcEOUxksCmVu0zA70NBJWlrMW1rPr/LRidFAflLI+s/w==", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.213.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/fetch-http-handler": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.2.tgz", - "integrity": "sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.0.tgz", + "integrity": "sha512-CicxWZxX6z35HR83jl+PLgtFgUrKRQ9LCXyxgenMnz5A1lgYWfAog7VtdOvGkJYyQgMNPhXQwkYrDLujk7z1Iw==", "dependencies": { - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/util-base64": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/hash-node": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.2.tgz", - "integrity": "sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/sdk-node/node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==" + }, + "node_modules/@opentelemetry/sdk-node/node_modules/import-in-the-middle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.0.tgz", + "integrity": "sha512-OnGy+eYT7wVejH2XWgLRgbmzujhhVIATQH0ztIeRilwHBjTeG3pD+XnH3PKX0r9gJ0BuJmJ68q/oh9qgXnNDQg==", "dependencies": { - "@smithy/types": "^4.2.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/invalid-dependency": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.2.tgz", - "integrity": "sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.0.tgz", + "integrity": "sha512-g/OZVkqlxllgFM7qMKqbPV9c1DUPhQ7d4n3pgZFcrnrNft9eJXZM2TNHTPYREJBrtNdRytYyvwjgL5geDKl3EQ==", "dependencies": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@aws-sdk/nested-clients/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", + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-content-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.2.tgz", - "integrity": "sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.6.0.tgz", + "integrity": "sha512-YhswtasmsbIGEFvLGvR9p/y3PVRTfFf+mgY8van4Ygpnv4sA3vooAjvh+qAn9PNWxs4/IwGGqiQS0PPsaRJ0vQ==", "dependencies": { - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "@opentelemetry/context-async-hooks": "2.6.0", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/sdk-trace-base": "2.6.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-endpoint": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.0.tgz", - "integrity": "sha512-xhLimgNCbCzsUppRTGXWkZywksuTThxaIB0HwbpsVLY5sceac4e1TZ/WKYqufQLaUy+gUSJGNdwD2jo3cXL0iA==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "dependencies": { - "@smithy/core": "^3.2.0", - "@smithy/middleware-serde": "^4.0.3", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "@smithy/util-middleware": "^4.0.2", - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-retry": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.0.tgz", - "integrity": "sha512-2zAagd1s6hAaI/ap6SXi5T3dDwBOczOMCSkkYzktqN1+tzbk1GAsHNAdo/1uzxz3Ky02jvZQwbi/vmDA6z4Oyg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/service-error-classification": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-retry": "^4.0.2", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.38.0.tgz", + "integrity": "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==", "engines": { - "node": ">=18.0.0" + "node": ">=14" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-serde": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.3.tgz", - "integrity": "sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==", - "license": "Apache-2.0", + "node_modules/@opentelemetry/sql-common": { + "version": "0.41.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.41.2.tgz", + "integrity": "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==", "dependencies": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-stack": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.2.tgz", - "integrity": "sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "node": "^18.19.0 || >=20.6.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@opentelemetry/api": "^1.1.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-config-provider": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.2.tgz", - "integrity": "sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "node_modules/@oxc-project/types": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.4.tgz", - "integrity": "sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/property-provider": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.2.tgz", - "integrity": "sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==", + "node_modules/@platformatic/basic": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/basic/-/basic-3.52.0.tgz", + "integrity": "sha512-wXn4/Y6kSr11iXOIsEbCBEfWXm23FdK++y8bJ07MqnakERn9Riac2IIXgZm8QHDWbUHWDrQBQBdEqWsg//3luA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "@fastify/error": "^4.0.0", + "@platformatic/foundation": "3.52.0", + "@platformatic/itc": "3.52.0", + "@platformatic/metrics": "3.52.0", + "@platformatic/telemetry": "3.52.0", + "execa": "^9.3.1", + "fast-json-patch": "^3.1.1", + "pino": "^9.9.0", + "pino-abstract-transport": "^2.0.0", + "semver": "^7.6.3", + "split2": "^4.2.0", + "undici": "^7.0.0", + "ws": "^8.18.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=22.19.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/protocol-http": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.0.tgz", - "integrity": "sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==", - "license": "Apache-2.0", + "node_modules/@platformatic/basic/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/querystring-builder": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.2.tgz", - "integrity": "sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.2.0", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" + "node": "^18.19.0 || >=20.5.0" }, - "engines": { - "node": ">=18.0.0" + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/querystring-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.2.tgz", - "integrity": "sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==", - "license": "Apache-2.0", + "node_modules/@platformatic/basic/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/service-error-classification": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.2.tgz", - "integrity": "sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.2.0" + "node": ">=18" }, - "engines": { - "node": ">=18.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.2.tgz", - "integrity": "sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==", + "node_modules/@platformatic/basic/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - }, "engines": { - "node": ">=18.0.0" + "node": ">=18.18.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/signature-v4": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.0.tgz", - "integrity": "sha512-4t5WX60sL3zGJF/CtZsUQTs3UrZEDO2P7pEaElrekbLqkWPYkgqNW1oeiNYC6xXifBnT9dVBOnNQRvOE9riU9w==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, + "node_modules/@platformatic/basic/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/smithy-client": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.2.0.tgz", - "integrity": "sha512-Qs65/w30pWV7LSFAez9DKy0Koaoh3iHhpcpCCJ4waj/iqwsuSzJna2+vYwq46yBaqO5ZbP9TjUsATUNxrKeBdw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.2.0", - "@smithy/middleware-endpoint": "^4.1.0", - "@smithy/middleware-stack": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "@smithy/util-stream": "^4.2.0", - "tslib": "^2.6.2" + "node": ">=18" }, - "engines": { - "node": ">=18.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/types": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", - "license": "Apache-2.0", + "node_modules/@platformatic/basic/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/url-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.2.tgz", - "integrity": "sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "node": ">=18" }, - "engines": { - "node": ">=18.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-base64": { + "node_modules/@platformatic/basic/node_modules/path-key": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@aws-sdk/nested-clients/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", + "node_modules/@platformatic/basic/node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" }, - "engines": { - "node": ">=18.0.0" + "bin": { + "pino": "bin.js" } }, - "node_modules/@aws-sdk/nested-clients/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", + "node_modules/@platformatic/basic/node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "split2": "^4.0.0" } }, - "node_modules/@aws-sdk/nested-clients/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" - }, + "node_modules/@platformatic/basic/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==", + "license": "ISC", "engines": { - "node": ">=18.0.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-config-provider": { + "node_modules/@platformatic/basic/node_modules/strip-final-newline": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", - "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "license": "MIT", "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.8.tgz", - "integrity": "sha512-ZTypzBra+lI/LfTYZeop9UjoJhhGRTg3pxrNpfSTQLd3AJ37r2z4AXTKpq1rFXiiUIJsYyFgNJdjWRGP/cbBaQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "node": ">=18" }, - "engines": { - "node": ">=18.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.8.tgz", - "integrity": "sha512-Rgk0Jc/UDfRTzVthye/k2dDsz5Xxs9LZaKCNPgJTRyoyBoeiNCnHsYGOyu1PKN+sDyPnJzMOz22JbwxzBp9NNA==", - "license": "Apache-2.0", + "node_modules/@platformatic/basic/node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", "dependencies": { - "@smithy/config-resolver": "^4.1.0", - "@smithy/credential-provider-imds": "^4.0.2", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "real-require": "^0.2.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-endpoints": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.2.tgz", - "integrity": "sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ==", + "node_modules/@platformatic/control": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/control/-/control-3.52.0.tgz", + "integrity": "sha512-hZmOnOcoe2/QjrdXUHMrVwb4Nqp/Ba2yXqffz/QCPT1IZ1zAmG28bNYX3PLWFZ+h+TkALU5onHVQjm1Hu7pNzQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" + "@fastify/error": "^4.0.0", + "@platformatic/foundation": "3.52.0", + "help-me": "^5.0.0", + "pino": "^9.9.0", + "pino-pretty": "^13.0.0", + "table": "^6.8.1", + "undici": "^7.0.0", + "ws": "^8.16.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=22.19.0" } }, - "node_modules/@aws-sdk/nested-clients/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", + "node_modules/@platformatic/control/node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" }, - "engines": { - "node": ">=18.0.0" + "bin": { + "pino": "bin.js" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-middleware": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.2.tgz", - "integrity": "sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==", - "license": "Apache-2.0", + "node_modules/@platformatic/control/node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "split2": "^4.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-retry": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.2.tgz", - "integrity": "sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg==", - "license": "Apache-2.0", + "node_modules/@platformatic/control/node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", "dependencies": { - "@smithy/service-error-classification": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "real-require": "^0.2.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.0.tgz", - "integrity": "sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==", - "license": "Apache-2.0", + "node_modules/@platformatic/flame": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@platformatic/flame/-/flame-1.6.0.tgz", + "integrity": "sha512-xVPFOznGrW+YZw3KbLTeoQsTy/XlDmfqkdbp8p7LZZLFJHnA0FhfMLxGbOrtTaAwVrs3Zp0r0fGTJ06O+AlJ0g==", + "license": "MIT", "dependencies": { - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/node-http-handler": "^4.0.4", - "@smithy/types": "^4.2.0", - "@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" + "@datadog/pprof": "^5.9.0", + "fastify": "^5.5.0", + "pprof-format": "^2.2.1", + "pprof-to-md": "^0.1.0", + "react-pprof": "^1.4.0" + }, + "bin": { + "flame": "bin/flame.js" }, "engines": { - "node": ">=18.0.0" + "node": ">=22.6.0" } }, - "node_modules/@aws-sdk/nested-clients/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==", + "node_modules/@platformatic/foundation": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/foundation/-/foundation-3.52.0.tgz", + "integrity": "sha512-w2tw/Cm8VLq8UtvrMz5cDpL+PM40D3xJgU3WcPSnQ3iSnfzPBjrAIpL/YwY8ueiIgUs0s8LfjWe4LSgq8QdUMg==", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@fastify/deepmerge": "^2.0.0", + "@fastify/error": "^4.0.0", + "@iarna/toml": "^2.2.5", + "@watchable/unpromise": "^1.0.2", + "ajv": "^8.12.0", + "boring-name-generator": "^1.0.3", + "colorette": "^2.0.19", + "debug": "^4.4.3", + "fast-json-patch": "^3.1.1", + "json5": "^2.2.3", + "leven": "~3.1.0", + "pino": "^9.9.0", + "pino-pretty": "^13.0.0", + "semver": "^7.6.3", + "undici": "7.24.0", + "yaml": "^2.4.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=22.19.0" } }, - "node_modules/@aws-sdk/nested-clients/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", + "node_modules/@platformatic/foundation/node_modules/@fastify/deepmerge": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-2.0.2.tgz", + "integrity": "sha512-3wuLdX5iiiYeZWP6bQrjqhrcvBIf0NHbQH1Ur1WbHvoiuTYUEItgygea3zs8aHpiitn0lOB8gX20u1qO+FDm7Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@platformatic/foundation/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" + "ms": "^2.1.3" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.654.0.tgz", - "integrity": "sha512-ydGOrXJxj3x0sJhsXyTmvJVLAE0xxuTWFJihTl67RtaO7VRNtd82I3P3bwoMMaDn5WpmV5mPo8fEUDRlBm3fPg==", + "node_modules/@platformatic/foundation/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/@platformatic/foundation/node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/types": "^3.4.2", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.6", - "tslib": "^2.6.2" + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" }, - "engines": { - "node": ">=16.0.0" + "bin": { + "pino": "bin.js" } }, - "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@platformatic/foundation/node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "split2": "^4.0.0" } }, - "node_modules/@aws-sdk/s3-presigned-post": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-presigned-post/-/s3-presigned-post-3.654.0.tgz", - "integrity": "sha512-MKpoLK4FV5/8Oydm3FQiSCbP6HoI75k2Z8z8MVK9bTiObedAMXTkdZDzq2qtqngEM4Lb1GgyJkActihJcv3AYw==", - "dev": true, + "node_modules/@platformatic/foundation/node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", "dependencies": { - "@aws-sdk/client-s3": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-format-url": "3.654.0", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/signature-v4": "^4.1.3", - "@smithy/types": "^3.4.2", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, + "real-require": "^0.2.0" + } + }, + "node_modules/@platformatic/foundation/node_modules/undici": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.0.tgz", + "integrity": "sha512-jxytwMHhsbdpBXxLAcuu0fzlQeXCNnWdDyRHpvWsUl8vd98UwYdl9YTyn8/HcpcJPC3pwUveefsa3zTxyD/ERg==", + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=20.18.1" } }, - "node_modules/@aws-sdk/s3-presigned-post/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dev": true, + "node_modules/@platformatic/generators": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/generators/-/generators-3.52.0.tgz", + "integrity": "sha512-J+0bs9vce+PGpyzVTwAnVWiXrjEItAYxX7bhzVVX0P0u0BXERi0sy0bklKjBR5tuY16NnteLczuAk0V6xk07/A==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@fastify/error": "^4.0.0", + "@platformatic/foundation": "3.52.0", + "change-case-all": "^2.1.0", + "execa": "^9.6.0", + "fastify": "^5.7.0", + "pino": "^9.9.0", + "undici": "^7.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=22.19.0" } }, - "node_modules/@aws-sdk/s3-request-presigner": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.654.0.tgz", - "integrity": "sha512-se1DllTTkaB85RSB60U/VUq5rCzwhqYZudxrf1zlWD0YjZpwKqifWgBomd3AyPZtQRQOcQooBcmZCVfGfdAuJQ==", + "node_modules/@platformatic/generators/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "license": "MIT", "dependencies": { - "@aws-sdk/signature-v4-multi-region": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-format-url": "3.654.0", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", + "node_modules/@platformatic/generators/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", "dependencies": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@aws-sdk/s3-request-presigner/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@platformatic/generators/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "license": "Apache-2.0", "engines": { - "node": ">=16.0.0" + "node": ">=18.18.0" } }, - "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.654.0.tgz", - "integrity": "sha512-f8kyvbzgD3lSK1kFc3jsDCYjdutcqGO3tOzYO/QIK7BTl5lxc4rm6IKTcF2UYJsn8jiNqih7tVK8aVIGi8IF/w==", - "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/protocol-http": "^4.1.3", - "@smithy/signature-v4": "^4.1.3", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, + "node_modules/@platformatic/generators/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", + "node_modules/@platformatic/generators/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", "dependencies": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@platformatic/generators/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/@platformatic/generators/node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", "dependencies": { - "@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" + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" }, - "engines": { - "node": ">=18.0.0" + "bin": { + "pino": "bin.js" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/generators/node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", "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" + "split2": "^4.0.0" + } + }, + "node_modules/@platformatic/generators/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==", + "license": "ISC", + "engines": { + "node": ">=14" }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@platformatic/generators/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/generators/node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "real-require": "^0.2.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/globals": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/globals/-/globals-3.52.0.tgz", + "integrity": "sha512-l/UI72POTtP1kTGBTiwZKqjdjwKFSEfI1f5xDgRyWpowwzax2Ud+75ZQqqyPWf5SmupNUNpV5KEi0zUOuT0ZCg==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@platformatic/prom-client": "^1.0.0", + "pino": "^9.9.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=22.19.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/globals/node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" }, - "engines": { - "node": ">=18.0.0" + "bin": { + "pino": "bin.js" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/globals/node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", "dependencies": { - "@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" - }, - "engines": { - "node": ">=18.0.0" + "split2": "^4.0.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/globals/node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", "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" + "real-require": "^0.2.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/http-metrics": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@platformatic/http-metrics/-/http-metrics-0.3.0.tgz", + "integrity": "sha512-e+wQJDCd9v7yKeV4u30ibAe5uGB14k+jDw1w9FqsM3tDgFY0UEHb24hJR9j2bVa6091e+ppGy3L4LhyUR92M0w==", + "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" - }, - "engines": { - "node": ">=18.0.0" + "@platformatic/prom-client": "^1.0.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/itc": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/itc/-/itc-3.52.0.tgz", + "integrity": "sha512-XwkT/9K5Mk6Br7xDqH9Zpun73n9GwIiIDEXJXgXPzq0Ek2IvUtaSDhWh02WiCIEvIHHFWXHauTd5olkL8eePbg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@fastify/error": "^4.0.0", + "@watchable/unpromise": "^1.0.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=22.19.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/metrics": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/metrics/-/metrics-3.52.0.tgz", + "integrity": "sha512-uFCWl1l/0WGtlhmwVQmb4c4ozCZl0xiDuskuZd//xYslhX+UUxs5XGVqFAr4PYJuYQyjooOs22WyXcqX6564xQ==", + "license": "Apache-2.0", "dependencies": { - "@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" + "@platformatic/http-metrics": "^0.3.0", + "@platformatic/prom-client": "^1.0.0", + "@platformatic/promotel": "^0.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=22.19.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/node": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/node/-/node-3.52.0.tgz", + "integrity": "sha512-d/F0kmgOHUe2/ZfqQChgbRclCEer+4LZRcFzCiOZyTnbaqs4Ffe2XVRFCemA/EwxvMisgIvKtVF6g5Y82iwc7g==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "@platformatic/basic": "3.52.0", + "@platformatic/foundation": "3.52.0", + "@platformatic/generators": "3.52.0", + "@watchable/unpromise": "^1.0.2", + "json5": "^2.2.3", + "light-my-request": "^6.0.0" + }, + "engines": { + "node": ">=22.19.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/prom-client": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@platformatic/prom-client/-/prom-client-1.0.0.tgz", + "integrity": "sha512-O7NfmdBWAm1QJ0LMrtcyCSgWmA+FQEiCyRqvouccmyAuydwLxLdmhcTTW3sEmO5f7bRfXpUVVgTtnFbIqEiHyw==", + "license": "Apache-2.0", "dependencies": { - "@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" + "@opentelemetry/api": "^1.4.0" }, "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "node": "^20 || ^22 || >=24" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/promotel": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@platformatic/promotel/-/promotel-0.1.0.tgz", + "integrity": "sha512-PpbXGiGef+zW9LAbD3JP1ehTDhRXXcrBz+cg1fd5kZamMqqf3leXSjOBlagqxaMk8e1M1GggnmB4RxGBvoAcGQ==", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "long": "^5.3.2", + "prom-client": "^15.1.3", + "protobufjs": "^7.4.0", + "undici": "^6.19.8" + }, + "bin": { + "promotel-server": "dist/commonjs/server.js" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/token-providers/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==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, + "node_modules/@platformatic/promotel/node_modules/undici": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", + "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=18.17" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/runtime": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/runtime/-/runtime-3.52.0.tgz", + "integrity": "sha512-/aC8MafEm6dornRlPZoaLlPGHfs/K1JLHgdg1zoC/yg6DpPfo/6MopD2hEb9Hm3aHdTJolmSZ77yzpS2sOes9Q==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "@fastify/accepts": "^5.0.0", + "@fastify/basic-auth": "^6.0.0", + "@fastify/error": "^4.0.0", + "@fastify/websocket": "^11.0.0", + "@opentelemetry/api": "^1.9.0", + "@platformatic/basic": "3.52.0", + "@platformatic/foundation": "3.52.0", + "@platformatic/generators": "3.52.0", + "@platformatic/itc": "3.52.0", + "@platformatic/metrics": "3.52.0", + "@platformatic/prom-client": "^1.0.0", + "@platformatic/telemetry": "3.52.0", + "@platformatic/undici-cache-memory": "^0.8.1", + "@watchable/unpromise": "^1.0.2", + "change-case-all": "^2.1.0", + "close-with-grace": "^2.3.0", + "colorette": "^2.0.20", + "cron": "^4.1.0", + "debounce": "^2.0.0", + "fastest-levenshtein": "^1.0.16", + "fastify": "^5.7.0", + "graphql": "^16.8.1", + "help-me": "^5.0.0", + "minimist": "^1.2.8", + "pino": "^10.1.0", + "pino-opentelemetry-transport": "^2.0.0", + "pino-pretty": "^13.0.0", + "semgrator": "^0.3.0", + "sonic-boom": "^4.2.0", + "systeminformation": "^5.27.11", + "undici": "^7.0.0", + "undici-thread-interceptor": "^1.3.1", + "ws": "^8.16.0" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@platformatic/runtime/node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" }, - "node_modules/@aws-sdk/token-providers/node_modules/@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", + "node_modules/@platformatic/telemetry": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/telemetry/-/telemetry-3.52.0.tgz", + "integrity": "sha512-QVdy0L0mDnRRhO5w1PJVMaZyx0TluLrN6pe8Lpsu7qBkfoAhxplGMDfxrscu7yXhnNur4X4U4IGGk0Vm5yiDkA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.0.8", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.3", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@fastify/swagger": "^9.5.1", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^2.0.1", + "@opentelemetry/exporter-trace-otlp-proto": "^0.203.0", + "@opentelemetry/exporter-zipkin": "^2.0.1", + "@opentelemetry/instrumentation": "^0.203.0", + "@opentelemetry/instrumentation-http": "^0.203.0", + "@opentelemetry/instrumentation-undici": "^0.14.0", + "@opentelemetry/resources": "^2.0.1", + "@opentelemetry/sdk-node": "^0.203.0", + "@opentelemetry/sdk-trace-base": "^2.0.1", + "@opentelemetry/semantic-conventions": "1.36.0", + "@platformatic/foundation": "3.52.0", + "fast-uri": "^3.0.6", + "fastify-plugin": "^5.0.1" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/api-logs": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.203.0.tgz", + "integrity": "sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, - "node_modules/@aws-sdk/token-providers/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==", - "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" - }, + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/context-async-hooks": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.0.1.tgz", + "integrity": "sha512-XuY23lSI3d4PEqKA+7SLtAgwqIfc6E/E9eAQWLN1vlpC53ybO3o6jW4BsXo1xvz9lYyyWItfQDDLzezER01mCw==", + "license": "Apache-2.0", "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/core": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", + "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", + "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" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.203.0.tgz", + "integrity": "sha512-g/2Y2noc/l96zmM+g0LdeuyYKINyBwN6FJySoU15LHPLcMN/1a0wNk2SegwKcxrRdE7Xsm7fkIR5n6XFe3QpPw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/sdk-logs": "0.203.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.203.0.tgz", + "integrity": "sha512-s0hys1ljqlMTbXx2XiplmMJg9wG570Z5lH7wMvrZX6lcODI56sG4HL03jklF63tBeyNwK2RV1/ntXGo3HgG4Qw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.203.0", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/sdk-logs": "0.203.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.203.0.tgz", + "integrity": "sha512-nl/7S91MXn5R1aIzoWtMKGvqxgJgepB/sH9qW0rZvZtabnsjbf8OQ1uSx3yogtvLr0GzwD596nQKz2fV7q2RBw==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.203.0", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-logs": "0.203.0", + "@opentelemetry/sdk-trace-base": "2.0.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.203.0.tgz", + "integrity": "sha512-FCCj9nVZpumPQSEI57jRAA89hQQgONuoC35Lt+rayWY/mzCAc6BQT7RFyFaZKJ2B7IQ8kYjOCPsF/HGFWjdQkQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.203.0", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-metrics": "2.0.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.203.0.tgz", + "integrity": "sha512-HFSW10y8lY6BTZecGNpV3GpoSy7eaO0Z6GATwZasnT4bEsILp8UJXNG5OmEsz4SdwCSYvyCbTJdNbZP3/8LGCQ==", + "license": "Apache-2.0", "dependencies": { - "@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" + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-metrics": "2.0.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.203.0.tgz", + "integrity": "sha512-OZnhyd9npU7QbyuHXFEPVm3LnjZYifuKpT3kTnF84mXeEQ84pJJZgyLBpU4FSkSwUkt/zbMyNAI7y5+jYTWGIg==", + "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" + "@opentelemetry/core": "2.0.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.203.0", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-metrics": "2.0.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/exporter-prometheus": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.203.0.tgz", + "integrity": "sha512-2jLuNuw5m4sUj/SncDf/mFPabUxMZmmYetx5RKIMIQyPnl6G6ooFzfeE8aXNRf8YD1ZXNlCnRPcISxjveGJHNg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-metrics": "2.0.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.203.0.tgz", + "integrity": "sha512-322coOTf81bm6cAA8+ML6A+m4r2xTCdmAZzGNTboPXRzhwPt4JEmovsFAs+grpdarObd68msOJ9FfH3jxM6wqA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.203.0.tgz", + "integrity": "sha512-ZDiaswNYo0yq/cy1bBLJFe691izEJ6IgNmkjm4C6kE9ub/OMQqDXORx2D2j8fzTBTxONyzusbaZlqtfmyqURPw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.203.0.tgz", + "integrity": "sha512-1xwNTJ86L0aJmWRwENCJlH4LULMG2sOXWIVw+Szta4fkqKVY50Eo4HoVKKq6U9QEytrWCr8+zjw0q/ZOeXpcAQ==", + "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" + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/exporter-zipkin": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.0.1.tgz", + "integrity": "sha512-a9eeyHIipfdxzCfc2XPrE+/TI3wmrZUDFtG2RRXHSbZZULAny7SyybSvaDvS77a7iib5MPiAvluwVvbGTsHxsw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/instrumentation": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.203.0.tgz", + "integrity": "sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.203.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/instrumentation-http": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.203.0.tgz", + "integrity": "sha512-y3uQAcCOAwnO6vEuNVocmpVzG3PER6/YZqbPbbffDdJ9te5NkHEkfSMNzlC3+v7KlE+WinPGc3N7MR30G1HY2g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.0.1", + "@opentelemetry/instrumentation": "0.203.0", + "@opentelemetry/semantic-conventions": "^1.29.0", + "forwarded-parse": "2.1.2" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/instrumentation-undici": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.14.0.tgz", + "integrity": "sha512-2HN+7ztxAReXuxzrtA3WboAKlfP5OsPA57KQn2AdYZbJ3zeRPcLXyW4uO/jpLE6PLm0QRtmeGCmfYpqRlwgSwg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.203.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.7.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.203.0.tgz", + "integrity": "sha512-Wbxf7k+87KyvxFr5D7uOiSq/vHXWommvdnNE7vECO3tAhsA2GfOlpWINCMWUEPdHZ7tCXxw6Epp3vgx3jU7llQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1" + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-transformer": "0.203.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.203.0.tgz", + "integrity": "sha512-te0Ze1ueJF+N/UOFl5jElJW4U0pZXQ8QklgSfJ2linHN0JJsuaHG8IabEUi2iqxY8ZBDlSiz1Trfv5JcjWWWwQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.203.0.tgz", + "integrity": "sha512-Y8I6GgoCna0qDQ2W6GCRtaF24SnvqvA8OfeTi7fqigD23u8Jpb4R5KFv/pRvrlGagcCLICMIyh9wiejp4TXu/A==", + "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" + "@opentelemetry/api-logs": "0.203.0", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-logs": "0.203.0", + "@opentelemetry/sdk-metrics": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1", + "protobufjs": "^7.3.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/propagator-b3": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.0.1.tgz", + "integrity": "sha512-Hc09CaQ8Tf5AGLmf449H726uRoBNGPBL4bjr7AnnUpzWMvhdn61F78z9qb6IqB737TffBsokGAK1XykFEZ1igw==", + "license": "Apache-2.0", "dependencies": { - "@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" + "@opentelemetry/core": "2.0.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/propagator-jaeger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.0.1.tgz", + "integrity": "sha512-7PMdPBmGVH2eQNb/AtSJizQNgeNTfh6jQFqys6lfhd6P4r+m/nTh3gKPPpaCXVdRQ+z93vfKk+4UGty390283w==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/core": "2.0.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/resources": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", + "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.0.1", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/sdk-logs": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.203.0.tgz", + "integrity": "sha512-vM2+rPq0Vi3nYA5akQD2f3QwossDnTDLvKbea6u/A2NZ3XDkPxMfo/PNrDoXhDUD/0pPo2CdH5ce/thn9K0kLw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.203.0", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", + "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/sdk-node": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.203.0.tgz", + "integrity": "sha512-zRMvrZGhGVMvAbbjiNQW3eKzW/073dlrSiAKPVWmkoQzah9wfynpVPeL55f9fVIm0GaBxTLcPeukWGy0/Wj7KQ==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.203.0", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/exporter-logs-otlp-grpc": "0.203.0", + "@opentelemetry/exporter-logs-otlp-http": "0.203.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.203.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.203.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.203.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.203.0", + "@opentelemetry/exporter-prometheus": "0.203.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.203.0", + "@opentelemetry/exporter-trace-otlp-http": "0.203.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.203.0", + "@opentelemetry/exporter-zipkin": "2.0.1", + "@opentelemetry/instrumentation": "0.203.0", + "@opentelemetry/propagator-b3": "2.0.1", + "@opentelemetry/propagator-jaeger": "2.0.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-logs": "0.203.0", + "@opentelemetry/sdk-metrics": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1", + "@opentelemetry/sdk-trace-node": "2.0.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz", + "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.0.1.tgz", + "integrity": "sha512-UhdbPF19pMpBtCWYP5lHbTogLWx9N0EBxtdagvkn5YtsAnCBZzL7SjktG+ZmupRgifsHMjwUaCCaVmqGfSADmA==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/context-async-hooks": "2.0.1", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1" }, "engines": { - "node": ">=18.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@aws-sdk/token-providers/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==", - "dependencies": { - "@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" - }, + "node_modules/@platformatic/telemetry/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.36.0.tgz", + "integrity": "sha512-TtxJSRD8Ohxp6bKkhrm27JRHAxPczQA7idtcTOMYI+wQRRrfgqxHv1cFbCApcSnNjtXkmzFozn6jQtFrOmbjPQ==", + "license": "Apache-2.0", "engines": { - "node": ">=18.0.0" + "node": ">=14" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { - "@smithy/config-resolver": "^4.1.4", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "ms": "^2.1.3" }, "engines": { - "node": ">=18.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/node_modules/import-in-the-middle": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz", + "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/telemetry/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/@platformatic/telemetry/node_modules/require-in-the-middle": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", + "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.6.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@platformatic/undici-cache-memory": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@platformatic/undici-cache-memory/-/undici-cache-memory-0.8.4.tgz", + "integrity": "sha512-/JVfPhyUW0GQkmr5lGAGOgh0lpJbTcLG8VB2uNqgNgH1fhPPFAM0l4pLSyeDzpTj2r/GJPLqhzvCgangnSHfoQ==", + "license": "Apache-2.0" + }, + "node_modules/@platformatic/wattpm-pprof-capture": { + "version": "3.52.1", + "resolved": "https://registry.npmjs.org/@platformatic/wattpm-pprof-capture/-/wattpm-pprof-capture-3.52.1.tgz", + "integrity": "sha512-OHcHxSKmDba6RURkN4vwAoCE2H5zJULhhlntKOiJo5MdUK4Z5j8gVXvj/Zee5Nr4QWwYyNq4kwgoGCLLPC5tcw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@datadog/pprof": "^5.3.0", + "@fastify/error": "^4.0.0", + "undici": "^7.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=22.19.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", "dependencies": { - "@smithy/service-error-classification": "^4.0.6", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" } }, - "node_modules/@aws-sdk/token-providers/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==", + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@redis/client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", + "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", + "optional": true, "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" + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14" } }, - "node_modules/@aws-sdk/token-providers/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==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=18.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@aws-sdk/token-providers/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==", - "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=18.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@aws-sdk/token-providers/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" - } + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", + "cpu": [ + "x64" ], - "dependencies": { - "strnum": "^2.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@aws-sdk/token-providers/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" - } - ] - }, - "node_modules/@aws-sdk/types": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.654.0.tgz", - "integrity": "sha512-VWvbED3SV+10QJIcmU/PKjsKilsTV16d1I7/on4bvD/jo1qGeMXqLDBSen3ks/tuvXZF/mFc7ZW/W2DiLVtO7A==", - "dependencies": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=16.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@aws-sdk/types/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.568.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.568.0.tgz", - "integrity": "sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.654.0.tgz", - "integrity": "sha512-i902fcBknHs0Irgdpi62+QMvzxE+bczvILXigYrlHL4+PiEnlMVpni5L5W1qCkNZXf8AaMrSBuR1NZAGp6UOUw==", - "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/types": "^3.4.2", - "@smithy/util-endpoints": "^2.1.2", - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@aws-sdk/util-endpoints/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@aws-sdk/util-format-url": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.654.0.tgz", - "integrity": "sha512-2yAlJ/l1uTJhS52iu4+/EvdIyQhDBL+nATY8rEjFI0H+BHGVrJIH2CL4DByhvi2yvYwsqQX0HYah6pF/yoXukA==", - "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/querystring-builder": "^3.0.6", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@aws-sdk/util-format-url/node_modules/@smithy/querystring-builder": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.6.tgz", - "integrity": "sha512-sQe08RunoObe+Usujn9+R2zrLuQERi3CWvRO3BvnoWSYUaIrLKuAIeY7cMeDax6xGyfIP3x/yFWbEKSXvOnvVg==", - "dependencies": { - "@smithy/types": "^3.4.2", - "@smithy/util-uri-escape": "^3.0.0", - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@aws-sdk/util-format-url/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@aws-sdk/util-format-url/node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "tslib": "^2.6.2" + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" }, "engines": { - "node": ">=16.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.568.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", - "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", + "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "tslib": "^2.6.2" + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=16.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.654.0.tgz", - "integrity": "sha512-ykYAJqvnxLt7wfrqya28wuH3/7NdrwzfiFd7NqEVQf7dXVxL5RPEpD7DxjcyQo3DsHvvdUvGZVaQhozycn1pzA==", - "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/types": "^3.4.2", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@aws-sdk/util-user-agent-browser/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", + "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, + "node_modules/@shopify/semaphore": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@shopify/semaphore/-/semaphore-3.1.0.tgz", + "integrity": "sha512-LxonkiWEu12FbZhuOMhsdocpxCqm7By8C/2U9QgNuEoXUx2iMrlXjJv3p93RwfNC6TrdlNRo17gRer1z1309VQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "engines": { - "node": ">=16.0.0" + "node": ">=18.12.0" } }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.654.0.tgz", - "integrity": "sha512-a0ojjdBN6pqv6gB4H/QPPSfhs7mFtlVwnmKCM/QrTaFzN0U810PJ1BST3lBx5sa23I5jWHGaoFY+5q65C3clLQ==", - "dependencies": { - "@aws-sdk/types": "3.654.0", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" + "node": ">=18" }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@aws-sdk/util-user-agent-node/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@smithy/abort-controller": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.1.1.tgz", + "integrity": "sha512-1+qdrUqLhaALYL0iOcN43EP6yAXXQ2wWZ6taf4S2pNGowmOc5gx+iMQv+E42JizNJjB0+gEadOXeV1Bf7JWL1Q==", "dependencies": { - "tslib": "^2.6.2" + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.654.0.tgz", - "integrity": "sha512-qA2diK3d/ztC8HUb7NwPKbJRV01NpzTzxFn+L5G3HzJBNeKbjLcprQ/9uG9gp2UEx2Go782FI1ddrMNa0qBICA==", + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.2.tgz", + "integrity": "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/xml-builder/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.3.tgz", + "integrity": "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==", + "license": "Apache-2.0", "dependencies": { + "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/config-resolver": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.13.tgz", + "integrity": "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/config-resolver/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/core": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", - "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/config-resolver/node_modules/@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "license": "Apache-2.0", "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.10", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.10", - "@babel/parser": "^7.26.10", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.10", - "@babel/types": "^7.26.10", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "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, - "bin": { - "semver": "bin/semver.js" + "node": ">=18.0.0" } }, - "node_modules/@babel/generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/core": { + "version": "3.23.13", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.13.tgz", + "integrity": "sha512-J+2TT9D6oGsUVXVEMvz8h2EmdVnkBiy2auCie4aSJMvKlzUtO5hqjEzXhoCUkIMo7gAYjbQcN0g/MMSXEhDs1Q==", + "license": "Apache-2.0", "dependencies": { - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-stream": "^4.5.21", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/core/node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/core/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.25.9" + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", - "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/core/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/compat-data": "^7.26.8", - "@babel/helper-validator-option": "^7.25.9", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.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/@smithy/core/node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", "dependencies": { - "yallist": "^3.0.2" - } - }, - "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, - "bin": { - "semver": "bin/semver.js" + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "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-create-class-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", - "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/core/node_modules/@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.27.0", - "semver": "^6.3.1" + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/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": ">=18.0.0" } }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.0.tgz", - "integrity": "sha512-fO8l08T76v48BhpNRW/nQ0MxfnSdoSKUJBMjubOAYffsVuGG5qOfMq7N6Es7UJvi7Y8goXXo07EfcHZXDPuELQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/core/node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "regexpu-core": "^6.2.0", - "semver": "^6.3.1" + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/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": ">=18.0.0" } }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", - "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.12.tgz", + "integrity": "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/credential-provider-imds/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.12.tgz", + "integrity": "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA==", + "license": "Apache-2.0", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.13.1", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-codec/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-codec/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.12.tgz", + "integrity": "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", - "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-serde-browser/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-wrap-function": "^7.25.9", - "@babel/traverse": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", - "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.12.tgz", + "integrity": "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.26.5" + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-serde-config-resolver/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.12.tgz", + "integrity": "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-serde-node/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.12.tgz", + "integrity": "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", - "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-serde-universal/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/helpers": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", - "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.15", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.15.tgz", + "integrity": "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==", + "license": "Apache-2.0", "dependencies": { - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0" + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/fetch-http-handler/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { - "@babel/types": "^7.27.0" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", - "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/fetch-http-handler/node_modules/@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", - "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/fetch-http-handler/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", - "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/fetch-http-handler/node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/hash-blob-browser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.13.tgz", + "integrity": "sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9" + "@smithy/chunked-blob-reader": "^5.2.2", + "@smithy/chunked-blob-reader-native": "^4.2.3", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", - "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/hash-blob-browser/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" + "node_modules/@smithy/hash-node": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.12.tgz", + "integrity": "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "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, + "node_modules/@smithy/hash-node/node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "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, + "node_modules/@smithy/hash-node/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.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/@smithy/hash-node/node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", - "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/hash-node/node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/util-buffer-from": "^4.2.2", + "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.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", - "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/hash-stream-node": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.12.tgz", + "integrity": "sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/types": "^4.13.1", + "@smithy/util-utf8": "^4.2.2", + "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, + "node_modules/@smithy/hash-stream-node/node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "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, + "node_modules/@smithy/hash-stream-node/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", - "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/hash-stream-node/node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/is-array-buffer": "^4.2.2", + "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, + "node_modules/@smithy/hash-stream-node/node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@smithy/util-buffer-from": "^4.2.2", + "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, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.12.tgz", + "integrity": "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@smithy/types": "^4.13.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, + "node_modules/@smithy/invalid-dependency/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "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, + "node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=16.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, + "node_modules/@smithy/md5-js": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.12.tgz", + "integrity": "sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@smithy/types": "^4.13.1", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.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, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "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, + "node_modules/@smithy/md5-js/node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "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.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", - "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/md5-js/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "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-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/md5-js/node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", - "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/md5-js/node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", - "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.12.tgz", + "integrity": "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-remap-async-to-generator": "^7.25.9", - "@babel/traverse": "^7.26.8" + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.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-transform-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", - "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-content-length/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9" + "@smithy/types": "^4.13.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-transform-block-scoped-functions": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", - "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-content-length/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.0.tgz", - "integrity": "sha512-u1jGphZ8uDI2Pj/HJj6YQ6XQLZCNjOlprjxB5SVz6rq2T6SwAR+CdrWK0CP7F+9rDVMXdB0+r6Am5G5aobOjAQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.28", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.28.tgz", + "integrity": "sha512-p1gfYpi91CHcs5cBq982UlGlDrxoYUX6XdHSo91cQ2KFuz6QloHosO7Jc60pJiVmkWrKOV8kFYlGFFbQ2WUKKQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@smithy/core": "^3.23.13", + "@smithy/middleware-serde": "^4.2.16", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-middleware": "^4.2.12", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", - "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-endpoint/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", - "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-endpoint/node_modules/@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", - "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-retry": { + "version": "4.4.46", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.46.tgz", + "integrity": "sha512-SpvWNNOPOrKQGUqZbEPO+es+FRXMWvIyzUKUOYdDgdlA6BdZj/R58p4umoQ76c2oJC44PiM7mKizyyex1IJzow==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "globals": "^11.1.0" + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/service-error-classification": "^4.2.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.13", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-classes/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-retry/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=4" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", - "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-retry/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/template": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", - "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-retry/node_modules/@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/types": "^4.13.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-transform-dotall-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", - "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-serde": { + "version": "4.2.16", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.16.tgz", + "integrity": "sha512-beqfV+RZ9RSv+sQqor3xroUUYgRFCGRw6niGstPG8zO9LgTl0B0MCucxjmrH/2WwksQN7UUgI7KNANoZv+KALA==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/core": "^3.23.13", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.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-transform-duplicate-keys": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", - "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-serde/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/types": "^4.13.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-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-serde/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", - "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-stack": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.12.tgz", + "integrity": "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/types": "^4.13.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-transform-exponentiation-operator": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", - "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/middleware-stack/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", - "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/node-config-provider": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.12.tgz", + "integrity": "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.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-transform-for-of": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz", - "integrity": "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/node-config-provider/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", - "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/node-http-handler": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.3.1.tgz", + "integrity": "sha512-gLA8qK2nL9J0Rk/WEZSvgin4AppvuCYRYg61dcUo/uKxvMZsMInL5I5ZdJTogOvdfVug3N2dgI5ffcUfS4S9PA==", "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@smithy/abort-controller": "^2.1.1", + "@smithy/protocol-http": "^3.1.1", + "@smithy/querystring-builder": "^2.1.1", + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=14.0.0" } }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", - "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/property-provider": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.12.tgz", + "integrity": "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/types": "^4.13.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-transform-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", - "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/property-provider/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", - "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/protocol-http": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.1.1.tgz", + "integrity": "sha512-6ZRTSsaXuSL9++qEwH851hJjUA0OgXdQFCs+VDw4tGH256jQ3TjYY/i34N4vd24RV3nrjNsgd1yhb57uMoKbzQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/types": "^2.9.1", + "tslib": "^2.5.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=14.0.0" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", - "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/querystring-builder": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.1.1.tgz", + "integrity": "sha512-C/ko/CeEa8jdYE4gt6nHO5XDrlSJ3vdCG0ZAc6nD5ZIE7LBp0jCx4qoqp7eoutBu7VrGMXERSRoPqwi1WjCPbg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/types": "^2.9.1", + "@smithy/util-uri-escape": "^2.1.1", + "tslib": "^2.5.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=14.0.0" } }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", - "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/querystring-parser": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.12.tgz", + "integrity": "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/types": "^4.13.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-transform-modules-commonjs": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", - "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/querystring-parser/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", - "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/service-error-classification": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.12.tgz", + "integrity": "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@smithy/types": "^4.13.1" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", - "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/service-error-classification/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.7.tgz", + "integrity": "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", - "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/shared-ini-file-loader/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.26.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", - "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/signature-v4": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.12.tgz", + "integrity": "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", - "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/signature-v4/node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", - "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/signature-v4/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9" + "@smithy/types": "^4.13.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-transform-object-super": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", - "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/signature-v4/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", - "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", - "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/types": "^4.13.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-transform-private-methods": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", - "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", - "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/signature-v4/node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", - "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/smithy-client": { + "version": "4.12.8", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.8.tgz", + "integrity": "sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/core": "^3.23.13", + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-stream": "^4.5.21", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.0.tgz", - "integrity": "sha512-LX/vCajUJQDqE7Aum/ELUMZAY19+cDpghxrnyt5I1tV6X5PyC86AOoWXWFYFeIvauyeSA6/ktn4tQVn/3ZifsA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/smithy-client/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "regenerator-transform": "^0.15.2" + "@smithy/types": "^4.13.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-transform-regexp-modifiers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", - "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/smithy-client/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", - "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/types": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.9.1.tgz", + "integrity": "sha512-vjXlKNXyprDYDuJ7UW5iobdmyDm6g8dDG+BFUncAg/3XJaN45Gy5RWWWUVgrzIK7S4R1KWgIX5LeJcfvSI24bw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.5.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=14.0.0" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", - "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/url-parser": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.12.tgz", + "integrity": "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/querystring-parser": "^4.2.12", + "@smithy/types": "^4.13.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-transform-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", - "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/url-parser/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", - "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-base64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz", - "integrity": "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-base64/node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.0.tgz", - "integrity": "sha512-+LLkxA9rKJpNoGsbLnAgOCdESl73vwYn+V6b+5wHbrE7OGKVDPHIQvbFSzqE6rwqaCw2RE+zdJrlLkcf8YOA0w==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-base64/node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz", - "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-base64/node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.27.0", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-syntax-typescript": "^7.25.9" + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", - "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", - "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", - "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=16.0.0" } }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", - "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/preset-env": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz", - "integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.44", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.44.tgz", + "integrity": "sha512-eZg6XzaCbVr2S5cAErU5eGBDaOVTuTo1I65i4tQcHENRcZ8rMWhQy1DaIYUSLyZjsfXvmCqZrstSMYyGFocvHA==", + "license": "Apache-2.0", "dependencies": { - "@babel/compat-data": "^7.26.8", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.26.0", - "@babel/plugin-syntax-import-attributes": "^7.26.0", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.25.9", - "@babel/plugin-transform-async-generator-functions": "^7.26.8", - "@babel/plugin-transform-async-to-generator": "^7.25.9", - "@babel/plugin-transform-block-scoped-functions": "^7.26.5", - "@babel/plugin-transform-block-scoping": "^7.25.9", - "@babel/plugin-transform-class-properties": "^7.25.9", - "@babel/plugin-transform-class-static-block": "^7.26.0", - "@babel/plugin-transform-classes": "^7.25.9", - "@babel/plugin-transform-computed-properties": "^7.25.9", - "@babel/plugin-transform-destructuring": "^7.25.9", - "@babel/plugin-transform-dotall-regex": "^7.25.9", - "@babel/plugin-transform-duplicate-keys": "^7.25.9", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-dynamic-import": "^7.25.9", - "@babel/plugin-transform-exponentiation-operator": "^7.26.3", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-for-of": "^7.26.9", - "@babel/plugin-transform-function-name": "^7.25.9", - "@babel/plugin-transform-json-strings": "^7.25.9", - "@babel/plugin-transform-literals": "^7.25.9", - "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", - "@babel/plugin-transform-member-expression-literals": "^7.25.9", - "@babel/plugin-transform-modules-amd": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.26.3", - "@babel/plugin-transform-modules-systemjs": "^7.25.9", - "@babel/plugin-transform-modules-umd": "^7.25.9", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-new-target": "^7.25.9", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", - "@babel/plugin-transform-numeric-separator": "^7.25.9", - "@babel/plugin-transform-object-rest-spread": "^7.25.9", - "@babel/plugin-transform-object-super": "^7.25.9", - "@babel/plugin-transform-optional-catch-binding": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9", - "@babel/plugin-transform-private-methods": "^7.25.9", - "@babel/plugin-transform-private-property-in-object": "^7.25.9", - "@babel/plugin-transform-property-literals": "^7.25.9", - "@babel/plugin-transform-regenerator": "^7.25.9", - "@babel/plugin-transform-regexp-modifiers": "^7.26.0", - "@babel/plugin-transform-reserved-words": "^7.25.9", - "@babel/plugin-transform-shorthand-properties": "^7.25.9", - "@babel/plugin-transform-spread": "^7.25.9", - "@babel/plugin-transform-sticky-regex": "^7.25.9", - "@babel/plugin-transform-template-literals": "^7.26.8", - "@babel/plugin-transform-typeof-symbol": "^7.26.7", - "@babel/plugin-transform-unicode-escapes": "^7.25.9", - "@babel/plugin-transform-unicode-property-regex": "^7.25.9", - "@babel/plugin-transform-unicode-regex": "^7.25.9", - "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.40.0", - "semver": "^6.3.1" + "@smithy/property-provider": "^4.2.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/preset-env/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/@smithy/util-defaults-mode-browser/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.48", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.48.tgz", + "integrity": "sha512-FqOKTlqSaoV3nzO55pMs5NBnZX8EhoI0DGmn9kbYeXWppgHD6dchyuj2HLqp4INJDJbSrj6OFYJkAh/WhSzZPg==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" + "@smithy/config-resolver": "^4.4.13", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/preset-typescript": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.0.tgz", - "integrity": "sha512-vxaPFfJtHhgeOVXRKuHpHPAOgymmy8V8I65T1q53R7GCZlefKeCaTyDs3zOPHTTbmquvNlQYC5klEvWsBAtrBQ==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.26.3", - "@babel/plugin-transform-typescript": "^7.27.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.3.tgz", + "integrity": "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", - "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", - "license": "MIT", + "node_modules/@smithy/util-endpoints/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", "dependencies": { - "regenerator-runtime": "^0.14.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.0.0" } }, - "node_modules/@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-middleware": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.11.tgz", + "integrity": "sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow==", + "license": "Apache-2.0", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, + "node_modules/@smithy/util-middleware/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=4" + "node": ">=16.0.0" } }, - "node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", - "dev": true, - "license": "MIT", + "node_modules/@smithy/util-retry": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.13.tgz", + "integrity": "sha512-qQQsIvL0MGIbUjeSrg0/VlQ3jGNKyM3/2iU3FPNgy01z+Sp4OvcaxbgIoFOTvB61ZoohtutuOvOcgmhbD0katQ==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@smithy/service-error-classification": "^4.2.12", + "@smithy/types": "^4.13.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/@smithy/util-retry/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], + "node_modules/@smithy/util-stream": { + "version": "4.5.21", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.21.tgz", + "integrity": "sha512-KzSg+7KKywLnkoKejRtIBXDmwBfjGvg1U1i/etkC7XSWUyFCoLno1IohV2c74IzQqdhX5y3uE44r/8/wuK+A7Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/node-http-handler": "^4.5.1", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "node_modules/@smithy/util-stream/node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "node_modules/@smithy/util-stream/node_modules/@smithy/node-http-handler": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.1.tgz", + "integrity": "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "node_modules/@smithy/util-stream/node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/@smithy/util-stream/node_modules/@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/@smithy/util-stream/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "node_modules/@smithy/util-stream/node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "node_modules/@smithy/util-stream/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/util-stream/node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/util-stream/node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/util-uri-escape": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.1.1.tgz", + "integrity": "sha512-saVzI1h6iRBUVSqtnlOnc9ssU09ypo7n+shdQ8hBTZno/9rZ3AuRYvoHInV57VF7Qn7B+pFJG7qTzFiHxWlWBw==", + "dependencies": { + "tslib": "^2.5.0" + }, "engines": { - "node": ">=18" + "node": ">=14.0.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=16.0.0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/util-waiter": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.14.tgz", + "integrity": "sha512-2zqq5o/oizvMaFUlNiTyZ7dbgYv1a893aGut2uaxtbzTx/VYYnRxWzDHuD/ftgcw94ffenua+ZNLrbqwUYE+Bg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/util-waiter/node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@smithy/uuid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18" + "node": ">=18.0.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "cpu": [ - "s390x" - ], + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "MIT" + }, + "node_modules/@tus/file-store": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tus/file-store/-/file-store-2.0.0.tgz", + "integrity": "sha512-LTh9L/RoWoo2TbBGPZOuhuyEIIqweoTekT77ZkIVkpYkLK8zTt++PRdY+VyJsLDbFMO9RzvKSBRmj1H8SPdDew==", + "dependencies": { + "@tus/utils": "^0.6.0", + "debug": "^4.3.4" + }, "engines": { - "node": ">=18" + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@redis/client": "^1.6.0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@tus/s3-store": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@tus/s3-store/-/s3-store-2.0.2.tgz", + "integrity": "sha512-wXrlb8k0BikpSs5FuMSTjEOK0QrL+xBEqPrmROcLOWMAaximPHtzHjvG6QDc5UbjBaV8PTqMnWhtXj85vvn3tw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@aws-sdk/client-s3": "^3.758.0", + "@shopify/semaphore": "^3.1.0", + "@tus/utils": "^0.6.0", + "debug": "^4.3.4", + "multistream": "^4.1.0" + }, "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "node_modules/@tus/server": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@tus/server/-/server-2.2.1.tgz", + "integrity": "sha512-VLf74cpRsk1H3UdAs3mwBfWsw5aI0bUYKpkzjVhdw/yJGZlcOGLxz0FdmkgpIvW+8tBHulMog+v6dg5rCAmIAw==", + "dependencies": { + "@tus/utils": "^0.6.0", + "debug": "^4.3.4", + "lodash.throttle": "^4.1.1", + "set-cookie-parser": "^2.7.1", + "srvx": "^0.2.8" + }, "engines": { - "node": ">=18" + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@redis/client": "^1.6.0", + "ioredis": "^5.4.1" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "node_modules/@tus/utils": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@tus/utils/-/utils-0.6.0.tgz", + "integrity": "sha512-GpMpAQfVdC4UDhpsZrRPjGpdXg+JW5MquqMqtObUVsORwLBV6XI67iTT5be+z98THdqb6dl3bTLIElIdgPeo2g==", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "cpu": [ - "arm64" - ], + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "cpu": [ - "x64" - ], + "node_modules/@types/async-retry": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@types/async-retry/-/async-retry-1.4.5.tgz", + "integrity": "sha512-YrdjSD+yQv7h6d5Ip+PMxh3H6ZxKyQk0Ts+PvaNRInxneG9PFVZjFg77ILAN+N6qYf7g4giSJ1l+ZjQ1zeegvA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/retry": "*" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "cpu": [ - "arm64" - ], + "node_modules/@types/aws-lambda": { + "version": "8.10.161", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.161.tgz", + "integrity": "sha512-rUYdp+MQwSFocxIOcSsYSF3YYYC/uUpMbCY/mbO21vGqfrEYvNSoPyKYDj6RhXXpPfS0KstW9RwG3qXh9sL7FQ==" + }, + "node_modules/@types/bunyan": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.11.tgz", + "integrity": "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/busboy": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.3.0.tgz", + "integrity": "sha512-Qx7ehfGO/k2yiTVpRIVIu16oVgbJpG65WLjEhbNSoTPdQEoRCorFUKHsSznyHmIkdvzOg2W3JWeewCmfSwcgeA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/node": "*" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "cpu": [ - "x64" - ], + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "cpu": [ - "arm64" - ], + "node_modules/@types/cloneable-readable": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/cloneable-readable/-/cloneable-readable-2.0.3.tgz", + "integrity": "sha512-+Ihof4L4iu9k4WTzYbJSkzUxt6f1wzXn6u48fZYxgST+BsC9bBHTOJ59Buy1/4sC9j7ZWF7bxDf/n/mrtk/nzw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/node": "*" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "cpu": [ - "ia32" - ], + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, + "license": "MIT" + }, + "node_modules/@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==" + }, + "node_modules/@types/json-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.4.tgz", + "integrity": "sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, + "node_modules/@types/luxon": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz", + "integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==", + "license": "MIT" + }, + "node_modules/@types/memcached": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@types/memcached/-/memcached-2.2.10.tgz", + "integrity": "sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mysql": { + "version": "2.15.27", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz", + "integrity": "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "24.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", + "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "undici-types": "~7.16.0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "node_modules/@types/oracledb": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@types/oracledb/-/oracledb-6.5.2.tgz", + "integrity": "sha512-kK1eBS/Adeyis+3OlBDMeQQuasIDLUYXsi2T15ccNJ0iyUpQ4xDF7svFu3+bGVrI0CMBUclPciz+lsQR3JX3TQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/pg": { + "version": "8.15.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz", + "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/pg-pool": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.7.tgz", + "integrity": "sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==", + "dependencies": { + "@types/pg": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true + }, + "node_modules/@types/stream-buffers": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.7.tgz", + "integrity": "sha512-azOCy05sXVXrO+qklf0c/B07H/oHaIuDDAiHPVwlk3A9Ek+ksHyTeMajLZl3r76FxpPpxem//4Te61G1iW3Giw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/tedious": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", + "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/xml2js": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", + "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.4.tgz", + "integrity": "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.4", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "funding": { + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@vitest/browser": "4.1.4", + "vitest": "4.1.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", - "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "node_modules/@vitest/coverage-v8/node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@vitest/expect": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", + "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", "dev": true, + "license": "MIT", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://opencollective.com/vitest" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@vitest/pretty-format": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", + "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", "dev": true, + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "tinyrainbow": "^3.1.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://opencollective.com/vitest" } }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "node_modules/@vitest/runner": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", + "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.4", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@fastify/accept-negotiator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", - "integrity": "sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/@fastify/accepts": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@fastify/accepts/-/accepts-5.0.2.tgz", - "integrity": "sha512-pX0OrioMz3C2cuYFOGRCNMdP3sR6daTFjeSNFrWlZVutawpPIGI5opK5h4Qa6x6C9oavdDkAjA16orneE2jAFQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], + "node_modules/@vitest/snapshot": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", + "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", + "dev": true, "license": "MIT", "dependencies": { - "accepts": "^1.3.8", - "fastify-plugin": "^5.0.0" + "@vitest/pretty-format": "4.1.4", + "@vitest/utils": "4.1.4", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@fastify/ajv-compiler": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.2.tgz", - "integrity": "sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], + "node_modules/@vitest/spy": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", + "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", + "dev": true, "license": "MIT", - "dependencies": { - "ajv": "^8.12.0", - "ajv-formats": "^3.0.1", - "fast-uri": "^3.0.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@fastify/busboy": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.1.tgz", - "integrity": "sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==", - "license": "MIT" + "node_modules/@vitest/utils": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", + "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.4", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, - "node_modules/@fastify/deepmerge": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-3.1.0.tgz", - "integrity": "sha512-lCVONBQINyNhM6LLezB6+2afusgEYR4G8xenMsfe+AT+iZ7Ca6upM5Ha8UkZuYSnuMw3GWl/BiPXnLMi/gSxuQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" + "node_modules/@watchable/unpromise": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@watchable/unpromise/-/unpromise-1.0.2.tgz", + "integrity": "sha512-yGCKYzCrAfJQ9yzm76r1bl4WUIWyqmh4vqidXn5LyOfPbgdiZrKOyvW2ivqIvtmsRVb7u3ModEpc4q901VRgXw==" }, - "node_modules/@fastify/error": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", - "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true }, - "node_modules/@fastify/fast-json-stringify-compiler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", - "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "dependencies": { - "fast-json-stringify": "^6.0.0" + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" } }, - "node_modules/@fastify/forwarded": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.0.tgz", - "integrity": "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==", - "license": "MIT" + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" }, - "node_modules/@fastify/merge-json-schemas": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", - "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dependencies": { - "dequal": "^2.0.3" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/@fastify/multipart": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-9.2.1.tgz", - "integrity": "sha512-U4221XDMfzCUtfzsyV1/PkR4MNgKI0158vUUyn/oF2Tl6RxMc+N7XYLr5fZXQiEC+Fmw5zFaTjxsTGTgtDtK+g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/busboy": "^3.0.0", - "@fastify/deepmerge": "^3.0.0", - "@fastify/error": "^4.0.0", - "fastify-plugin": "^5.0.0", - "secure-json-parse": "^4.0.0" + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, - "node_modules/@fastify/multipart/node_modules/secure-json-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.0.0.tgz", - "integrity": "sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "peerDependencies": { + "acorn": "^8" + } }, - "node_modules/@fastify/proxy-addr": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.0.0.tgz", - "integrity": "sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==", + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", - "dependencies": { - "@fastify/forwarded": "^3.0.0", - "ipaddr.js": "^2.1.0" + "engines": { + "node": ">= 14" } }, - "node_modules/@fastify/rate-limit": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@fastify/rate-limit/-/rate-limit-10.3.0.tgz", - "integrity": "sha512-eIGkG9XKQs0nyynatApA3EVrojHOuq4l6fhB4eeCk4PIOeadvOJz9/4w3vGI44Go17uaXOWEcPkaD8kuKm7g6Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", "dependencies": { - "@lukeed/ms": "^2.0.2", - "fastify-plugin": "^5.0.0", - "toad-cache": "^3.7.0" + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" } }, - "node_modules/@fastify/send": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@fastify/send/-/send-4.1.0.tgz", - "integrity": "sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { - "@lukeed/ms": "^2.0.2", - "escape-html": "~1.0.3", - "fast-decode-uri-component": "^1.0.1", - "http-errors": "^2.0.0", - "mime": "^3" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@fastify/static": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.2.0.tgz", - "integrity": "sha512-PejC/DtT7p1yo3p+W7LiUtLMsV8fEvxAK15sozHy9t8kwo5r0uLYmhV/inURmGz1SkHZFz/8CNtHLPyhKcx4SQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true } - ], + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "dev": true, "license": "MIT", "dependencies": { - "@fastify/accept-negotiator": "^2.0.0", - "@fastify/send": "^4.0.0", - "content-disposition": "^0.5.4", - "fastify-plugin": "^5.0.0", - "fastq": "^1.17.1", - "glob": "^11.0.0" + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" } }, - "node_modules/@fastify/swagger": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-9.5.1.tgz", - "integrity": "sha512-EGjYLA7vDmCPK7XViAYMF6y4+K3XUy5soVTVxsyXolNe/Svb4nFQxvtuQvvoQb2Gzc9pxiF3+ZQN/iZDHhKtTg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", "dependencies": { - "fastify-plugin": "^5.0.0", - "json-schema-resolver": "^3.0.0", - "openapi-types": "^12.1.3", - "rfdc": "^1.3.1", - "yaml": "^2.4.2" + "retry": "0.13.1" } }, - "node_modules/@fastify/swagger-ui": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@fastify/swagger-ui/-/swagger-ui-5.2.3.tgz", - "integrity": "sha512-e7ivEJi9EpFcxTONqICx4llbpB2jmlI+LI1NQ/mR7QGQnyDOqZybPK572zJtcdHZW4YyYTBHcP3a03f1pOh0SA==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.2.0.tgz", + "integrity": "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==", "funding": [ { "type": "github", @@ -11528,4232 +11428,4130 @@ ], "license": "MIT", "dependencies": { - "@fastify/static": "^8.0.0", - "fastify-plugin": "^5.0.0", - "openapi-types": "^12.1.3", - "rfdc": "^1.3.1", - "yaml": "^2.4.1" + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" } }, - "node_modules/@grpc/grpc-js": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.11.2.tgz", - "integrity": "sha512-DWp92gDD7/Qkj7r8kus6/HCINeo3yPZWZ3paKgDgsbKbSpoxKg1yvN8xe2Q8uE3zOsPe3bX8FQX2+XValq2yTw==", + "node_modules/aws-sigv4-sign": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/aws-sigv4-sign/-/aws-sigv4-sign-1.2.1.tgz", + "integrity": "sha512-iS0pV4xGzhexBCMG9ggXM5CaxTHa3KxOxkw2tphLgA/60vSycSWjJWso0s4xzGRrtABSi0b3LlxG5Jek7NjuqA==", "dependencies": { - "@grpc/proto-loader": "^0.7.13", - "@js-sdsl/ordered-map": "^4.4.2" + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-sdk/credential-provider-node": "^3.609.0", + "@smithy/protocol-http": "^4.0.3", + "@smithy/signature-v4": "^3.1.2" }, "engines": { - "node": ">=12.10.0" + "node": ">=18" } }, - "node_modules/@grpc/proto-loader": { - "version": "0.7.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", - "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "node_modules/aws-sigv4-sign/node_modules/@smithy/protocol-http": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", + "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + "@smithy/types": "^3.7.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6" + "node": ">=16.0.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, + "node_modules/aws-sigv4-sign/node_modules/@smithy/signature-v4": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-3.1.2.tgz", + "integrity": "sha512-3BcPylEsYtD0esM4Hoyml/+s7WP2LFhcM3J2AGdcL2vx9O60TtfpDOL72gjb4lU8NeRPeKAwR77YNyyGvMbuEA==", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=16.0.0" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true - }, - "node_modules/@ioredis/commands": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", - "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" - }, - "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==", + "node_modules/aws-sigv4-sign/node_modules/@smithy/types": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", "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" + "tslib": "^2.6.2" }, "engines": { - "node": ">=12" + "node": ">=16.0.0" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" + "node_modules/aws-sigv4-sign/node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "dependencies": { + "tslib": "^2.6.2" }, - "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==", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=16.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==" - }, - "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==", + "node_modules/axios": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "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" + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.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==", + "node_modules/axios-retry": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.9.1.tgz", + "integrity": "sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w==", "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" + "@babel/runtime": "^7.15.4", + "is-retry-allowed": "^2.2.0" + } + }, + "node_modules/b4a": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.2.tgz", + "integrity": "sha512-DyUOdz+E8R6+sruDpQNOaV0y/dBbV6X/8ZkxrDcR0Ifc3BgKlpgG0VAtfOozA0eMtJO5GGe9FsZhueLs00pTww==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } } }, - "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==", + "node_modules/bare-events": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.7.0.tgz", + "integrity": "sha512-b3N5eTW1g7vXkw+0CXh/HazGTcO5KYuu/RCNaJbDMPI6LHDi+7qe8EmxKUVe1sUbY2KZOVZFyj62x0OEz9qyAA==", + "license": "Apache-2.0" + }, + "node_modules/bare-fs": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.4.4.tgz", + "integrity": "sha512-Q8yxM1eLhJfuM7KXVP3zjhBvtMJCYRByoTT+wHXjpdMELv0xICFJX+1w4c7csa+WZEOsq4ItJ4RGwvzid6m/dw==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" }, "engines": { - "node": ">=12" + "bare": ">=1.16.0" }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } } }, - "node_modules/@isaacs/ttlcache": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", - "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "node_modules/bare-os": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "license": "Apache-2.0", + "optional": true, "engines": { - "node": ">=12" + "bare": ">=1.14.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, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "optional": true, "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" + "bare-os": "^3.0.1" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, + "node_modules/bare-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", + "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "sprintf-js": "~1.0.2" - } + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, + "node_modules/bare-url": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.2.2.tgz", + "integrity": "sha512-g+ueNGKkrjMazDG3elZO1pNs3HY5+mMmOet1jtKyhOaCnkLzitxf26z7hoAEkDNgdNmnc1KIlt/dw6Po6xZMpA==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "bare-path": "^3.0.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/batch2": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/batch2/-/batch2-1.0.6.tgz", + "integrity": "sha512-xZsZx73HfBcoUMITZwqRF+gO5RGx5Sf+hZjmxoRuu8xpYM003aSDM7uwKKbmATJG1Kuc5Rs0kck/0ALpOnue0w==", + "dependencies": { + "through2": "^3.0.1" + }, "engines": { - "node": ">=8" + "node": ">=8.6.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, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", "engines": { - "node": ">=8" + "node": "*" } }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "license": "MIT", + "node_modules/bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==", + "license": "MIT" + }, + "node_modules/boring-name-generator": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/boring-name-generator/-/boring-name-generator-1.0.3.tgz", + "integrity": "sha512-1wEo1pNahY9js7Vkp1RQa/VWdWrXYJnVAmsHV3Pw/0YzspjABLw7dcekjukOMTIYWr8ir/aG0GX1eoEkYhpnUg==", + "license": "ISC", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" + "commander": "^6.1.0", + "lodash": "^4.17.20" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "bin": { + "boring-name-generator": "src/generator-bin.js" } }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, + "node_modules/boring-name-generator/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">= 6" } }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" + "fill-range": "^7.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "license": "MIT", + "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==", "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" } }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, - "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3" + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" } }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@jest/reporters/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "*" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "license": "MIT" + }, + "node_modules/change-case-all": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/change-case-all/-/change-case-all-2.1.0.tgz", + "integrity": "sha512-v6b0WWWkZUMHVuYk82l+WROgkUm4qEN2w5hKRNWtEOYwWqUGoi8C6xH0l1RLF1EoWqDFK6MFclmN3od6ws3/uw==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "change-case": "^5.2.0", + "sponge-case": "^2.0.2", + "swap-case": "^3.0.2", + "title-case": "^3.0.3" } }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "license": "MIT", + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, + "node_modules/close-with-grace": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/close-with-grace/-/close-with-grace-2.5.0.tgz", + "integrity": "sha512-MewUtZQU6N4YVHIne63zGtjIQzTINgr6lQp2Y0CutaCw2FsdYahW57dH1Wdz+aV5ipbBzEBZD5znwX2NooS+IA==", + "license": "MIT" + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "color-name": "~1.1.4" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=7.0.0" } }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" + }, + "node_modules/combine-errors": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/combine-errors/-/combine-errors-3.0.3.tgz", + "integrity": "sha512-C8ikRNRMygCwaTx+Ek3Yr+OuZzgZjduCOfSQBjbM8V3MfgcjSTeto/GXP6PAwKvJz/v15b7GHZvx5rOlczFw/Q==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "custom-error-instance": "2.1.1", + "lodash.uniqby": "4.5.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" + "delayed-stream": "~1.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">= 0.8" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "engines": { - "node": ">=6.0.0" + "node": ">=14" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" } }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/@lukeed/ms": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", - "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", - "engines": { - "node": ">=8" + "url": "https://opencollective.com/express" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-5.0.0.tgz", + "integrity": "sha512-lCDbA+ZqVFQGUj7h9QBKoIpLhl8iihkO0nCTyRNzuXtcd7ubODpYB04IFy31JloiJgG0Uovu8ot8oxRzn7Nwtw==", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "compare-func": "^2.0.0", + "lodash": "^4.17.15", + "q": "^1.5.1" }, "engines": { - "node": ">= 8" + "node": ">=10" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, + "node_modules/cookie-es": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-2.0.0.tgz", + "integrity": "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==" + }, + "node_modules/cron": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-4.4.0.tgz", + "integrity": "sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==", + "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@types/luxon": "~3.7.0", + "luxon": "~3.7.0" }, "engines": { - "node": ">= 8" + "node": ">=18.x" + }, + "funding": { + "type": "ko-fi", + "url": "https://ko-fi.com/intcreator" } }, - "node_modules/@opentelemetry/api": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz", - "integrity": "sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==", + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "dependencies": { + "luxon": "^3.2.1" + }, "engines": { - "node": ">=8.0.0" + "node": ">=12.0.0" } }, - "node_modules/@opentelemetry/api-logs": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", - "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": { - "@opentelemetry/api": "^1.0.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=14" + "node": ">= 8" } }, - "node_modules/@opentelemetry/auto-instrumentations-node": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.50.0.tgz", - "integrity": "sha512-LqoSiWrOM4Cnr395frDHL4R/o5c2fuqqrqW8sZwhxvkasImmVlyL66YMPHllM2O5xVj2nP2ANUKHZSd293meZA==", - "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/instrumentation-amqplib": "^0.42.0", - "@opentelemetry/instrumentation-aws-lambda": "^0.44.0", - "@opentelemetry/instrumentation-aws-sdk": "^0.44.0", - "@opentelemetry/instrumentation-bunyan": "^0.41.0", - "@opentelemetry/instrumentation-cassandra-driver": "^0.41.0", - "@opentelemetry/instrumentation-connect": "^0.39.0", - "@opentelemetry/instrumentation-cucumber": "^0.9.0", - "@opentelemetry/instrumentation-dataloader": "^0.12.0", - "@opentelemetry/instrumentation-dns": "^0.39.0", - "@opentelemetry/instrumentation-express": "^0.42.0", - "@opentelemetry/instrumentation-fastify": "^0.39.0", - "@opentelemetry/instrumentation-fs": "^0.15.0", - "@opentelemetry/instrumentation-generic-pool": "^0.39.0", - "@opentelemetry/instrumentation-graphql": "^0.43.0", - "@opentelemetry/instrumentation-grpc": "^0.53.0", - "@opentelemetry/instrumentation-hapi": "^0.41.0", - "@opentelemetry/instrumentation-http": "^0.53.0", - "@opentelemetry/instrumentation-ioredis": "^0.43.0", - "@opentelemetry/instrumentation-kafkajs": "^0.3.0", - "@opentelemetry/instrumentation-knex": "^0.40.0", - "@opentelemetry/instrumentation-koa": "^0.43.0", - "@opentelemetry/instrumentation-lru-memoizer": "^0.40.0", - "@opentelemetry/instrumentation-memcached": "^0.39.0", - "@opentelemetry/instrumentation-mongodb": "^0.47.0", - "@opentelemetry/instrumentation-mongoose": "^0.42.0", - "@opentelemetry/instrumentation-mysql": "^0.41.0", - "@opentelemetry/instrumentation-mysql2": "^0.41.0", - "@opentelemetry/instrumentation-nestjs-core": "^0.40.0", - "@opentelemetry/instrumentation-net": "^0.39.0", - "@opentelemetry/instrumentation-pg": "^0.44.0", - "@opentelemetry/instrumentation-pino": "^0.42.0", - "@opentelemetry/instrumentation-redis": "^0.42.0", - "@opentelemetry/instrumentation-redis-4": "^0.42.0", - "@opentelemetry/instrumentation-restify": "^0.41.0", - "@opentelemetry/instrumentation-router": "^0.40.0", - "@opentelemetry/instrumentation-socket.io": "^0.42.0", - "@opentelemetry/instrumentation-tedious": "^0.14.0", - "@opentelemetry/instrumentation-undici": "^0.6.0", - "@opentelemetry/instrumentation-winston": "^0.40.0", - "@opentelemetry/resource-detector-alibaba-cloud": "^0.29.1", - "@opentelemetry/resource-detector-aws": "^1.6.1", - "@opentelemetry/resource-detector-azure": "^0.2.11", - "@opentelemetry/resource-detector-container": "^0.4.1", - "@opentelemetry/resource-detector-gcp": "^0.29.11", - "@opentelemetry/resources": "^1.24.0", - "@opentelemetry/sdk-node": "^0.53.0" - }, + "node_modules/custom-error-instance": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz", + "integrity": "sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg==", + "dev": true + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.4.1" + "node": ">= 12" } }, - "node_modules/@opentelemetry/context-async-hooks": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.26.0.tgz", - "integrity": "sha512-HedpXXYzzbaoutw6DFLWLDket2FwLkLpil4hGCZ1xYEIMTcivdfwEOISgdbLEWyG3HW52gTq2V9mOVJrONgiwg==", + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "node": "*" } }, - "node_modules/@opentelemetry/core": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.26.0.tgz", - "integrity": "sha512-1iKxXXE8415Cdv0yjG3G6hQnB5eVEsJce3QaawX8SjDn0mAS0ZM8fAbZZJD4ajvhC15cePvosSCut404KrIIvQ==", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.27.0" - }, + "node_modules/debounce": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz", + "integrity": "sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=18" }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.53.0.tgz", - "integrity": "sha512-x5ygAQgWAQOI+UOhyV3z9eW7QU2dCfnfOuIBiyYmC2AWr74f6x/3JBnP27IAcEx6aihpqBYWKnpoUTztkVPAZw==", + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/sdk-logs": "0.53.0" + "ms": "2.1.2" }, "engines": { - "node": ">=14" + "node": ">=6.0" }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@opentelemetry/exporter-logs-otlp-http": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.53.0.tgz", - "integrity": "sha512-cSRKgD/n8rb+Yd+Cif6EnHEL/VZg1o8lEcEwFji1lwene6BdH51Zh3feAD9p2TyVoBKrl6Q9Zm2WltSp2k9gWQ==", + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/sdk-logs": "0.53.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": ">=14" + "node": ">= 0.4" }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@opentelemetry/exporter-logs-otlp-proto": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.53.0.tgz", - "integrity": "sha512-jhEcVL1deeWNmTUP05UZMriZPSWUBcfg94ng7JuBb1q2NExgnADQFl1VQQ+xo62/JepK+MxQe4xAwlsDQFbISA==", - "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-trace-base": "1.26.0" - }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "node": ">=0.4.0" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.53.0.tgz", - "integrity": "sha512-m6KSh6OBDwfDjpzPVbuJbMgMbkoZfpxYH2r262KckgX9cMYvooWXEKzlJYsNDC6ADr28A1rtRoUVRwNfIN4tUg==", - "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0" - }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "node": ">=0.10" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-http": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.53.0.tgz", - "integrity": "sha512-m7F5ZTq+V9mKGWYpX8EnZ7NjoqAU7VemQ1E2HAG+W/u0wpY1x0OmbxAXfGKFHCspdJk8UKlwPGrpcB8nay3P8A==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0" - }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "node": ">= 0.8" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.53.0.tgz", - "integrity": "sha512-T/bdXslwRKj23S96qbvGtaYOdfyew3TjPEKOk5mHjkCmkVl1O9C/YMdejwSsdLdOq2YW30KjR9kVi0YMxZushQ==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0" - }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "node": ">=6" } }, - "node_modules/@opentelemetry/exporter-zipkin": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.26.0.tgz", - "integrity": "sha512-PW5R34n3SJHO4t0UetyHKiXL6LixIqWN6lWncg3eRXhKuT30x+b7m5sDJS0kEWRfHeS+kG7uCw2vBzmB2lk3Dw==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "node": ">=8" } }, - "node_modules/@opentelemetry/instrumentation": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", - "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@types/shimmer": "^1.2.0", - "import-in-the-middle": "^1.8.1", - "require-in-the-middle": "^7.1.1", - "semver": "^7.5.2", - "shimmer": "^1.2.1" + "is-obj": "^2.0.0" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=8" } }, - "node_modules/@opentelemetry/instrumentation-amqplib": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.42.0.tgz", - "integrity": "sha512-fiuU6OKsqHJiydHWgTRQ7MnIrJ2lEqsdgFtNIH4LbAUJl/5XmrIeoDzDnox+hfkgWK65jsleFuQDtYb5hW1koQ==", - "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, + "node_modules/dotenv": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz", + "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=12" } }, - "node_modules/@opentelemetry/instrumentation-aws-lambda": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.44.0.tgz", - "integrity": "sha512-6vmr7FJIuopZzsQjEQTp4xWtF6kBp7DhD7pPIN8FN0dKUKyuVraABIpgWjMfelaUPy4rTYUGkYqPluhG0wx8Dw==", - "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/propagator-aws-xray": "^1.3.1", - "@opentelemetry/resources": "^1.8.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/aws-lambda": "8.10.143" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-aws-sdk": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.44.0.tgz", - "integrity": "sha512-HIWFg4TDQsayceiikOnruMmyQ0SZYW6WiR+wknWwWVLHC3lHTCpAnqzp5V42ckArOdlwHZu2Jvq2GMSM4Myx3w==", + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/propagation-utils": "^0.30.11", - "@opentelemetry/semantic-conventions": "^1.27.0" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">= 0.4" } }, - "node_modules/@opentelemetry/instrumentation-bunyan": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.41.0.tgz", - "integrity": "sha512-NoQS+gcwQ7pzb2PZFyra6bAxDAVXBMmpKxBblEuXJWirGrAksQllg9XTdmqhrwT/KxUYrbVca/lMams7e51ysg==", + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", "dependencies": { - "@opentelemetry/api-logs": "^0.53.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@types/bunyan": "1.8.9" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" } }, - "node_modules/@opentelemetry/instrumentation-cassandra-driver": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.41.0.tgz", - "integrity": "sha512-hvTNcC8qjCQEHZTLAlTmDptjsEGqCKpN+90hHH8Nn/GwilGr5TMSwGrlfstdJuZWyw8HAnRUed6bcjvmHHk2Xw==", - "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "node_modules/@opentelemetry/instrumentation-connect": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.39.0.tgz", - "integrity": "sha512-pGBiKevLq7NNglMgqzmeKczF4XQMTOUOTkK8afRHMZMnrK3fcETyTH7lVaSozwiOM3Ws+SuEmXZT7DYrrhxGlg==", + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/connect": "3.4.36" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "once": "^1.4.0" } }, - "node_modules/@opentelemetry/instrumentation-cucumber": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.9.0.tgz", - "integrity": "sha512-4PQNFnIqnA2WM3ZHpr0xhZpHSqJ5xJ6ppTIzZC7wPqe+ZBpj41vG8B6ieqiPfq+im4QdqbYnzLb3rj48GDEN9g==", - "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "node": ">= 0.4" } }, - "node_modules/@opentelemetry/instrumentation-dataloader": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.12.0.tgz", - "integrity": "sha512-pnPxatoFE0OXIZDQhL2okF//dmbiWFzcSc8pUg9TqofCLYZySSxDCgQc69CJBo5JnI3Gz1KP+mOjS4WAeRIH4g==", - "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0" - }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">= 0.4" } }, - "node_modules/@opentelemetry/instrumentation-dns": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.39.0.tgz", - "integrity": "sha512-+iPzvXqVdJa67QBuz2tuP0UI3LS1/cMMo6dS7360DDtOQX+sQzkiN+mo3Omn4T6ZRhkTDw6c7uwsHBcmL31+1g==", + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "semver": "^7.5.4" + "es-errors": "^1.3.0" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">= 0.4" } }, - "node_modules/@opentelemetry/instrumentation-express": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.42.0.tgz", - "integrity": "sha512-YNcy7ZfGnLsVEqGXQPT+S0G1AE46N21ORY7i7yUQyfhGAL4RBjnZUqefMI0NwqIl6nGbr1IpF0rZGoN8Q7x12Q==", + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">= 0.4" } }, - "node_modules/@opentelemetry/instrumentation-fastify": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.39.0.tgz", - "integrity": "sha512-SS9uSlKcsWZabhBp2szErkeuuBDgxOUlllwkS92dVaWRnMmwysPhcEgHKB8rUe3BHg/GnZC1eo1hbTZv4YhfoA==", - "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=14" + "node": ">=18" }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" } }, - "node_modules/@opentelemetry/instrumentation-fs": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.15.0.tgz", - "integrity": "sha512-JWVKdNLpu1skqZQA//jKOcKdJC66TWKqa2FUFq70rKohvaSq47pmXlnabNO+B/BvLfmidfiaN35XakT5RyMl2Q==", - "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0" - }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=6" } }, - "node_modules/@opentelemetry/instrumentation-generic-pool": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.39.0.tgz", - "integrity": "sha512-y4v8Y+tSfRB3NNBvHjbjrn7rX/7sdARG7FuK6zR8PGb28CTa0kHpEGCJqvL9L8xkTNvTXo+lM36ajFGUaK1aNw==", - "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0" - }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=6" } }, - "node_modules/@opentelemetry/instrumentation-graphql": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.43.0.tgz", - "integrity": "sha512-aI3YMmC2McGd8KW5du1a2gBA0iOMOGLqg4s9YjzwbjFwjlmMNFSK1P3AIg374GWg823RPUGfVTIgZ/juk9CVOA==", + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@types/estree": "^1.0.0" } }, - "node_modules/@opentelemetry/instrumentation-grpc": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.53.0.tgz", - "integrity": "sha512-Ss338T92yE1UCgr9zXSY3cPuaAy27uQw+wAC5IwsQKCXL5wwkiOgkd+2Ngksa9EGsgUEMwGeHi76bDdHFJ5Rrw==", - "dependencies": { - "@opentelemetry/instrumentation": "0.53.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=6" } }, - "node_modules/@opentelemetry/instrumentation-hapi": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.41.0.tgz", - "integrity": "sha512-jKDrxPNXDByPlYcMdZjNPYCvw0SQJjN+B1A+QH+sx+sAHsKSAf9hwFiJSrI6C4XdOls43V/f/fkp9ITkHhKFbQ==", - "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=0.8.x" } }, - "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.53.0.tgz", - "integrity": "sha512-H74ErMeDuZfj7KgYCTOFGWF5W9AfaPnqLQQxeFq85+D29wwV2yqHbz2IKLYpkOh7EI6QwDEl7rZCIxjJLyc/CQ==", + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/instrumentation": "0.53.0", - "@opentelemetry/semantic-conventions": "1.27.0", - "semver": "^7.5.2" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "bare-events": "^2.7.0" } }, - "node_modules/@opentelemetry/instrumentation-ioredis": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.43.0.tgz", - "integrity": "sha512-i3Dke/LdhZbiUAEImmRG3i7Dimm/BD7t8pDDzwepSvIQ6s2X6FPia7561gw+64w+nx0+G9X14D7rEfaMEmmjig==", - "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/redis-common": "^0.36.2", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=12.0.0" } }, - "node_modules/@opentelemetry/instrumentation-kafkajs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.3.0.tgz", - "integrity": "sha512-UnkZueYK1ise8FXQeKlpBd7YYUtC7mM8J0wzUSccEfc/G8UqHQqAzIyYCUOUPUKp8GsjLnWOOK/3hJc4owb7Jg==", - "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, - "node_modules/@opentelemetry/instrumentation-knex": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.40.0.tgz", - "integrity": "sha512-6jka2jfX8+fqjEbCn6hKWHVWe++mHrIkLQtaJqUkBt3ZBs2xn1+y0khxiDS0v/mNb0bIKDJWwtpKFfsQDM1Geg==", + "node_modules/fast-copy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.2.tgz", + "integrity": "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==", + "license": "MIT" + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=8.6.0" } }, - "node_modules/@opentelemetry/instrumentation-koa": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.43.0.tgz", - "integrity": "sha512-lDAhSnmoTIN6ELKmLJBplXzT/Jqs5jGZehuG22EdSMaTwgjMpxMDI1YtlKEhiWPWkrz5LUsd0aOO0ZRc9vn3AQ==", + "node_modules/fast-json-parse": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-json-parse/-/fast-json-parse-1.0.3.tgz", + "integrity": "sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw==" + }, + "node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.3.0.tgz", + "integrity": "sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" } }, - "node_modules/@opentelemetry/instrumentation-lru-memoizer": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.40.0.tgz", - "integrity": "sha512-21xRwZsEdMPnROu/QsaOIODmzw59IYpGFmuC4aFWvMj6stA8+Ei1tX67nkarJttlNjoM94um0N4X26AD7ff54A==", + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "fast-decode-uri-component": "^1.0.1" } }, - "node_modules/@opentelemetry/instrumentation-memcached": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.39.0.tgz", - "integrity": "sha512-WfwvKAZ9I1qILRP5EUd88HQjwAAL+trXpCpozjBi4U6a0A07gB3fZ5PFAxbXemSjF5tHk9KVoROnqHvQ+zzFSQ==", + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-builder": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/memcached": "^2.2.6" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "path-expression-matcher": "^1.1.3" } }, - "node_modules/@opentelemetry/instrumentation-mongodb": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.47.0.tgz", - "integrity": "sha512-yqyXRx2SulEURjgOQyJzhCECSh5i1uM49NUaq9TqLd6fA7g26OahyJfsr9NE38HFqGRHpi4loyrnfYGdrsoVjQ==", + "node_modules/fast-xml-parser": { + "version": "5.5.8", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.8.tgz", + "integrity": "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/sdk-metrics": "^1.9.1", - "@opentelemetry/semantic-conventions": "^1.27.0" + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.2.0", + "strnum": "^2.2.0" }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "license": "MIT", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">= 4.9.1" } }, - "node_modules/@opentelemetry/instrumentation-mongoose": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.42.0.tgz", - "integrity": "sha512-AnWv+RaR86uG3qNEMwt3plKX1ueRM7AspfszJYVkvkehiicC3bHQA6vWdb6Zvy5HAE14RyFbu9+2hUUjR2NSyg==", + "node_modules/fastify": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.8.3.tgz", + "integrity": "sha512-XJXpRQ41+rsJ/GLeP9vyDC+fBXilcTlEXokMSexkdEkla4uf7ZQNaI5xl3el+kW5TZQulqYxLr659ey/KX7XmQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "@fastify/ajv-compiler": "^4.0.5", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^6.0.0", + "find-my-way": "^9.0.0", + "light-my-request": "^6.0.0", + "pino": "^9.14.0 || ^10.1.0", + "process-warning": "^5.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" } }, - "node_modules/@opentelemetry/instrumentation-mysql": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.41.0.tgz", - "integrity": "sha512-jnvrV6BsQWyHS2qb2fkfbfSb1R/lmYwqEZITwufuRl37apTopswu9izc0b1CYRp/34tUG/4k/V39PND6eyiNvw==", + "node_modules/fastify-plugin": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.1.0.tgz", + "integrity": "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/mysql": "2.15.26" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "reusify": "^1.0.4" } }, - "node_modules/@opentelemetry/instrumentation-mysql2": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.41.0.tgz", - "integrity": "sha512-REQB0x+IzVTpoNgVmy5b+UnH1/mDByrneimP6sbDHkp1j8QOl1HyWOrBH/6YWR0nrbU3l825Em5PlybjT3232g==", + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@opentelemetry/sql-common": "^0.40.1" + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": "^12.20 || >= 14.13" } }, - "node_modules/@opentelemetry/instrumentation-nestjs-core": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.40.0.tgz", - "integrity": "sha512-WF1hCUed07vKmf5BzEkL0wSPinqJgH7kGzOjjMAiTGacofNXjb/y4KQ8loj2sNsh5C/NN7s1zxQuCgbWbVTGKg==", + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "is-unicode-supported": "^2.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@opentelemetry/instrumentation-net": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.39.0.tgz", - "integrity": "sha512-rixHoODfI/Cx1B0mH1BpxCT0bRSxktuBDrt9IvpT2KSEutK5hR0RsRdgdz/GKk+BQ4u+IG6godgMSGwNQCueEA==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=8" } }, - "node_modules/@opentelemetry/instrumentation-pg": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.44.0.tgz", - "integrity": "sha512-oTWVyzKqXud1BYEGX1loo2o4k4vaU1elr3vPO8NZolrBtFvQ34nx4HgUaexUDuEog00qQt+MLR5gws/p+JXMLQ==", + "node_modules/find-my-way": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.5.0.tgz", + "integrity": "sha512-VW2RfnmscZO5KgBY5XVyKREMW5nMZcxDy+buTOsL+zIPnBlbKm+00sgzoQzq1EVh4aALZLfKdwv6atBGcjvjrQ==", + "license": "MIT", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@opentelemetry/sql-common": "^0.40.1", - "@types/pg": "8.6.1", - "@types/pg-pool": "2.0.6" + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=20" } }, - "node_modules/@opentelemetry/instrumentation-pg/node_modules/@types/pg": { - "version": "8.6.1", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", - "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dev": true, "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" + "micromatch": "^4.0.2" } }, - "node_modules/@opentelemetry/instrumentation-pino": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.42.0.tgz", - "integrity": "sha512-SoX6FzucBfTuFNMZjdurJhcYWq2ve8/LkhmyVLUW31HpIB45RF1JNum0u4MkGisosDmXlK4njomcgUovShI+WA==", - "dependencies": { - "@opentelemetry/api-logs": "^0.53.0", - "@opentelemetry/core": "^1.25.0", - "@opentelemetry/instrumentation": "^0.53.0" - }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=4.0" }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/@opentelemetry/instrumentation-redis": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.42.0.tgz", - "integrity": "sha512-jZBoqve0rEC51q0HuhjtZVq1DtUvJHzEJ3YKGvzGar2MU1J4Yt5+pQAQYh1W4jSoDyKeaI4hyeUdWM5N0c2lqA==", + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/redis-common": "^0.36.2", - "@opentelemetry/semantic-conventions": "^1.27.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">= 6" } }, - "node_modules/@opentelemetry/instrumentation-redis-4": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.42.0.tgz", - "integrity": "sha512-NaD+t2JNcOzX/Qa7kMy68JbmoVIV37fT/fJYzLKu2Wwd+0NCxt+K2OOsOakA8GVg8lSpFdbx4V/suzZZ2Pvdjg==", + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/redis-common": "^0.36.2", - "@opentelemetry/semantic-conventions": "^1.27.0" + "fetch-blob": "^3.1.2" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=12.20.0" } }, - "node_modules/@opentelemetry/instrumentation-restify": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.41.0.tgz", - "integrity": "sha512-gKEo+X/wVKUBuD2WDDlF7SlDNBHMWjSQoLxFCsGqeKgHR0MGtwMel8uaDGg9LJ83nKqYy+7Vl/cDFxjba6H+/w==", + "node_modules/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==" + }, + "node_modules/fs-extra": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", + "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "dev": true, "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=12" } }, - "node_modules/@opentelemetry/instrumentation-router": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.40.0.tgz", - "integrity": "sha512-bRo4RaclGFiKtmv/N1D0MuzO7DuxbeqMkMCbPPng6mDwzpHAMpHz/K/IxJmF+H1Hi/NYXVjCKvHGClageLe9eA==", - "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, + "node_modules/fs-xattr": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/fs-xattr/-/fs-xattr-0.3.1.tgz", + "integrity": "sha512-UVqkrEW0GfDabw4C3HOrFlxKfx0eeigfRne69FxSBdHIP8Qt5Sq6Pu3RM9KmMlkygtC4pPKkj5CiPO5USnj2GA==", + "hasInstallScript": true, + "os": [ + "!win32" + ], "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=8.6.0" } }, - "node_modules/@opentelemetry/instrumentation-socket.io": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.42.0.tgz", - "integrity": "sha512-xB5tdsBzuZyicQTO3hDzJIpHQ7V1BYJ6vWPWgl19gWZDBdjEGc3HOupjkd3BUJyDoDhbMEHGk2nNlkUU99EfkA==", - "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/@opentelemetry/instrumentation-tedious": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.14.0.tgz", - "integrity": "sha512-ofq7pPhSqvRDvD2FVx3RIWPj76wj4QubfrbqJtEx0A+fWoaYxJOCIQ92tYJh28elAmjMmgF/XaYuJuBhBv5J3A==", + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", "dependencies": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/tedious": "^4.0.14" + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=18" } }, - "node_modules/@opentelemetry/instrumentation-undici": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.6.0.tgz", - "integrity": "sha512-ABJBhm5OdhGmbh0S/fOTE4N69IZ00CsHC5ijMYfzbw3E5NwLgpQk5xsljaECrJ8wz1SfXbO03FiSuu5AyRAkvQ==", + "node_modules/gaxios/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "dependencies": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": ">=14" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "peerDependencies": { - "@opentelemetry/api": "^1.7.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, - "node_modules/@opentelemetry/instrumentation-winston": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.40.0.tgz", - "integrity": "sha512-eMk2tKl86YJ8/yHvtDbyhrE35/R0InhO9zuHTflPx8T0+IvKVUhPV71MsJr32sImftqeOww92QHt4Jd+a5db4g==", + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", "dependencies": { - "@opentelemetry/api-logs": "^0.53.0", - "@opentelemetry/instrumentation": "^0.53.0" + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "node": ">=18" } }, - "node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.53.0.tgz", - "integrity": "sha512-UCWPreGQEhD6FjBaeDuXhiMf6kkBODF0ZQzrk/tuQcaVDJ+dDQ/xhJp192H9yWnKxVpEjFrSSLnpqmX4VwX+eA==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-transformer": "0.53.0" - }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "optional": true, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "node": ">= 4" } }, - "node_modules/@opentelemetry/otlp-grpc-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.53.0.tgz", - "integrity": "sha512-F7RCN8VN+lzSa4fGjewit8Z5fEUpY/lmMVy5EWn2ZpbAabg3EE3sCLuTNfOiooNGnmvzimUPruoeqeko/5/TzQ==", - "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0" - }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/@opentelemetry/otlp-transformer": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.53.0.tgz", - "integrity": "sha512-rM0sDA9HD8dluwuBxLetUmoqGJKSAbWenwD65KY9iZhUxdBHRLrIdrABfNDP7aiTjcgK8XFyTn5fhDz7N+W6DA==", - "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-metrics": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "protobufjs": "^7.3.0" + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": ">=14" + "node": ">= 0.4" }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@opentelemetry/propagation-utils": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagation-utils/-/propagation-utils-0.30.11.tgz", - "integrity": "sha512-rY4L/2LWNk5p/22zdunpqVmgz6uN419DsRTw5KFMa6u21tWhXS8devlMy4h8m8nnS20wM7r6yYweCNNKjgLYJw==", + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "node": ">=8.0.0" } }, - "node_modules/@opentelemetry/propagator-aws-xray": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-aws-xray/-/propagator-aws-xray-1.26.0.tgz", - "integrity": "sha512-Sex+JyEZ/xX328TArBqQjh1NZSfNyw5NdASUIi9hnPsnMBMSBaDe7B9JRnXv0swz7niNyAnXa6MY7yOCV76EvA==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", "dependencies": { - "@opentelemetry/core": "1.26.0" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=14" + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/@opentelemetry/propagator-b3": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.26.0.tgz", - "integrity": "sha512-vvVkQLQ/lGGyEy9GT8uFnI047pajSOVnZI2poJqVGD3nJ+B9sFGdlHNnQKophE3lHfnIH0pw2ubrCTjZCgIj+Q==", + "node_modules/getopts": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", + "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==" + }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", "dependencies": { - "@opentelemetry/core": "1.26.0" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": ">=14" + "node": "18 || 20 || >=22" }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@opentelemetry/propagator-jaeger": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.26.0.tgz", - "integrity": "sha512-DelFGkCdaxA1C/QA0Xilszfr0t4YbGd3DjxiCDPh34lfnFr+VkkrjV9S8ZTJvAzfdKERXhfOxIKBoGPJwoSz7Q==", + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "dependencies": { - "@opentelemetry/core": "1.26.0" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "node": ">= 6" } }, - "node_modules/@opentelemetry/redis-common": { - "version": "0.36.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz", - "integrity": "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==", + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", "engines": { - "node": ">=14" + "node": "18 || 20 || >=22" } }, - "node_modules/@opentelemetry/resource-detector-alibaba-cloud": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.29.1.tgz", - "integrity": "sha512-Qshebw6azBuKUqGkVgambZlLS6Xh+LCoLXep1oqW1RSzSOHQxGYDsPs99v8NzO65eJHHOu8wc2yKsWZQAgYsSw==", + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dependencies": { - "@opentelemetry/resources": "^1.0.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "node": "18 || 20 || >=22" } }, - "node_modules/@opentelemetry/resource-detector-aws": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-1.6.1.tgz", - "integrity": "sha512-A/3lqx9xoew7sFi+AVUUVr6VgB7UJ5qqddkKR3gQk9hWLm1R7HUXVJG09cLcZ7DMNpX13DohPRGmHE/vp1vafw==", + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "license": "BlueOak-1.0.0", "dependencies": { - "@opentelemetry/core": "^1.0.0", - "@opentelemetry/resources": "^1.10.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=14" + "node": "18 || 20 || >=22" }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@opentelemetry/resource-detector-azure": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-azure/-/resource-detector-azure-0.2.11.tgz", - "integrity": "sha512-XepvQfTXWyHAoAziCfXGwYbSZL0LHtFk5iuKKN2VE2vzcoiw5Tepi0Qafuwb7CCtpQRReao4H7E29MFbCmh47g==", - "dependencies": { - "@opentelemetry/core": "^1.25.1", - "@opentelemetry/resources": "^1.10.1", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", "engines": { "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" } }, - "node_modules/@opentelemetry/resource-detector-container": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.4.1.tgz", - "integrity": "sha512-v0bvO6RxYtbxvY/HwqrPQnZ4UtP4nBq4AOyS30iqV2vEtiLTY1gNTbNvTF1lwN/gg/g5CY1tRSrHcYODDOv0vw==", - "dependencies": { - "@opentelemetry/resources": "^1.10.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">= 0.4" }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@opentelemetry/resource-detector-gcp": { - "version": "0.29.11", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.29.11.tgz", - "integrity": "sha512-07wJx4nyxD/c2z3n70OQOg8fmoO/baTsq8uU+f7tZaehRNQx76MPkRbV2L902N40Z21SPIG8biUZ30OXE9tOIg==", - "dependencies": { - "@opentelemetry/core": "^1.0.0", - "@opentelemetry/resources": "^1.0.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "gcp-metadata": "^6.0.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" }, - "node_modules/@opentelemetry/resources": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.26.0.tgz", - "integrity": "sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw==", - "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, + "node_modules/graphql": { + "version": "16.13.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", + "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==", + "license": "MIT", "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, - "node_modules/@opentelemetry/sdk-logs": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.53.0.tgz", - "integrity": "sha512-dhSisnEgIj/vJZXZV6f6KcTnyLDx/VuQ6l3ejuZpMpPlh9S1qMHiZU9NMmOkVkwwHkMy3G6mEBwdP23vUZVr4g==", - "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.4.0 <1.10.0" + "node": ">=8" } }, - "node_modules/@opentelemetry/sdk-metrics": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz", - "integrity": "sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ==", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" - }, - "engines": { - "node": ">=14" + "es-define-property": "^1.0.0" }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@opentelemetry/sdk-node": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.53.0.tgz", - "integrity": "sha512-0hsxfq3BKy05xGktwG8YdGdxV978++x40EAKyKr1CaHZRh8uqVlXnclnl7OMi9xLMJEcXUw7lGhiRlArFcovyg==", - "dependencies": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/exporter-logs-otlp-grpc": "0.53.0", - "@opentelemetry/exporter-logs-otlp-http": "0.53.0", - "@opentelemetry/exporter-logs-otlp-proto": "0.53.0", - "@opentelemetry/exporter-trace-otlp-grpc": "0.53.0", - "@opentelemetry/exporter-trace-otlp-http": "0.53.0", - "@opentelemetry/exporter-trace-otlp-proto": "0.53.0", - "@opentelemetry/exporter-zipkin": "1.26.0", - "@opentelemetry/instrumentation": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-metrics": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "@opentelemetry/sdk-trace-node": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">= 0.4" }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz", - "integrity": "sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", "dependencies": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" + "has-symbols": "^1.0.3" }, "engines": { - "node": ">=14" + "node": ">= 0.4" }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@opentelemetry/sdk-trace-node": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.26.0.tgz", - "integrity": "sha512-Fj5IVKrj0yeUwlewCRwzOVcr5avTuNnMHWf7GPc1t6WaT78J6CJyF3saZ/0RkZfdeNO8IcBl/bNcWMVZBMRW8Q==", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { - "@opentelemetry/context-async-hooks": "1.26.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/propagator-b3": "1.26.0", - "@opentelemetry/propagator-jaeger": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "semver": "^7.5.2" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "node": ">= 0.4" } }, - "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz", - "integrity": "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==", + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, + "node_modules/hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", + "license": "MIT", "engines": { "node": ">=14" } }, - "node_modules/@opentelemetry/sql-common": { - "version": "0.40.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz", - "integrity": "sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", "dependencies": { - "@opentelemetry/core": "^1.1.0" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": ">=14" + "node": ">= 0.8" }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "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==", - "optional": true, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, "engines": { - "node": ">=14" + "node": ">= 14" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "ms": "^2.0.0" } }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + "node_modules/hyperid": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/hyperid/-/hyperid-3.3.0.tgz", + "integrity": "sha512-7qhCVT4MJIoEsNcbhglhdmBKb09QtcmJNiIQGq7js/Khf5FtQQ9bzcAuloeqBeee7XD7JqDeve9KNlQya5tSGQ==", + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "uuid": "^8.3.2", + "uuid-parse": "^1.1.0" + } }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "node_modules/@redis/client": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", - "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", - "optional": true, + "node_modules/import-in-the-middle": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", + "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", "dependencies": { - "cluster-key-slot": "1.1.2", - "generic-pool": "3.9.0", - "yallist": "4.0.0" - }, - "engines": { - "node": ">=14" + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" } }, - "node_modules/@shopify/semaphore": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@shopify/semaphore/-/semaphore-3.1.0.tgz", - "integrity": "sha512-LxonkiWEu12FbZhuOMhsdocpxCqm7By8C/2U9QgNuEoXUx2iMrlXjJv3p93RwfNC6TrdlNRo17gRer1z1309VQ==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "engines": { - "node": ">=18.12.0" - } + "node_modules/import-in-the-middle/node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==" }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT" + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "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", - "dependencies": { - "type-detect": "4.0.8" + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "engines": { + "node": ">= 0.10" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@smithy/abort-controller": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.1.1.tgz", - "integrity": "sha512-1+qdrUqLhaALYL0iOcN43EP6yAXXQ2wWZ6taf4S2pNGowmOc5gx+iMQv+E42JizNJjB0+gEadOXeV1Bf7JWL1Q==", + "node_modules/ioredis": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz", + "integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==", "dependencies": { - "@smithy/types": "^2.9.1", - "tslib": "^2.5.0" + "@ioredis/commands": "^1.1.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" }, "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/chunked-blob-reader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-4.0.0.tgz", - "integrity": "sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ==", - "dependencies": { - "tslib": "^2.6.2" + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" } }, - "node_modules/@smithy/chunked-blob-reader-native": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.1.tgz", - "integrity": "sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ==", - "dependencies": { - "@smithy/util-base64": "^3.0.0", - "tslib": "^2.6.2" + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "engines": { + "node": ">= 12" } }, - "node_modules/@smithy/config-resolver": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.12.tgz", - "integrity": "sha512-YAJP9UJFZRZ8N+UruTeq78zkdjUHmzsY62J4qKWZ4SXB4QXJ/+680EfXXgkYA2xj77ooMqtUY9m406zGNqwivQ==", - "dependencies": { - "@smithy/node-config-provider": "^3.1.11", - "@smithy/types": "^3.7.1", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.10", - "tslib": "^2.6.2" - }, + "node_modules/ipaddr.js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">= 10" } }, - "node_modules/@smithy/config-resolver/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dependencies": { - "tslib": "^2.6.2" + "hasown": "^2.0.0" }, - "engines": { - "node": ">=16.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@smithy/core": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.4.tgz", - "integrity": "sha512-iFh2Ymn2sCziBRLPuOOxRPkuCx/2gBdXtBGuCUFLUe6bWYjKnhHyIPqGeNkLZ5Aco/5GjebRTBFiWID3sDbrKw==", - "dependencies": { - "@smithy/middleware-serde": "^3.0.10", - "@smithy/protocol-http": "^4.1.7", - "@smithy/types": "^3.7.1", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-middleware": "^3.0.10", - "@smithy/util-stream": "^3.3.1", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" }, "engines": { - "node": ">=16.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/core/node_modules/@smithy/protocol-http": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", - "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", - "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, "engines": { - "node": ">=16.0.0" + "node": ">=0.10.0" } }, - "node_modules/@smithy/core/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "engines": { - "node": ">=16.0.0" + "node": ">=8" } }, - "node_modules/@smithy/credential-provider-imds": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.7.tgz", - "integrity": "sha512-cEfbau+rrWF8ylkmmVAObOmjbTIzKyUC5TkBL58SbLywD0RCBC4JAUKbmtSm2w5KUJNRPGgpGFMvE2FKnuNlWQ==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "dependencies": { - "@smithy/node-config-provider": "^3.1.11", - "@smithy/property-provider": "^3.1.10", - "@smithy/types": "^3.7.1", - "@smithy/url-parser": "^3.0.10", - "tslib": "^2.6.2" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=0.10.0" } }, - "node_modules/@smithy/credential-provider-imds/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "engines": { - "node": ">=16.0.0" + "node": ">=0.12.0" } }, - "node_modules/@smithy/eventstream-codec": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.9.tgz", - "integrity": "sha512-F574nX0hhlNOjBnP+noLtsPFqXnWh2L0+nZKCwcu7P7J8k+k+rdIDs+RMnrMwrzhUE4mwMgyN0cYnEn0G8yrnQ==", - "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^3.7.1", - "@smithy/util-hex-encoding": "^3.0.0", - "tslib": "^2.6.2" + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" } }, - "node_modules/@smithy/eventstream-codec/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/eventstream-serde-browser": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.13.tgz", - "integrity": "sha512-Nee9m+97o9Qj6/XeLz2g2vANS2SZgAxV4rDBMKGHvFJHU/xz88x2RwCkwsvEwYjSX4BV1NG1JXmxEaDUzZTAtw==", - "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.12", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, + "node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", "engines": { - "node": ">=16.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/eventstream-serde-browser/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, "engines": { - "node": ">=16.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.10.tgz", - "integrity": "sha512-K1M0x7P7qbBUKB0UWIL5KOcyi6zqV5mPJoL0/o01HPJr0CSq3A9FYuJC6e11EX6hR8QTIR++DBiGrYveOu6trw==", - "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/eventstream-serde-config-resolver/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, "dependencies": { - "tslib": "^2.6.2" + "is-docker": "^2.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=8" } }, - "node_modules/@smithy/eventstream-serde-node": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.12.tgz", - "integrity": "sha512-kiZymxXvZ4tnuYsPSMUHe+MMfc4FTeFWJIc0Q5wygJoUQM4rVHNghvd48y7ppuulNMbuYt95ah71pYc2+o4JOA==", - "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.12", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "license": "MIT", + "peerDependencies": { + "ws": "*" } }, - "node_modules/@smithy/eventstream-serde-node/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=16.0.0" + "node": ">=8" } }, - "node_modules/@smithy/eventstream-serde-universal": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.12.tgz", - "integrity": "sha512-1i8ifhLJrOZ+pEifTlF0EfZzMLUGQggYQ6WmZ4d5g77zEKf7oZ0kvh1yKWHPjofvOwqrkwRDVuxuYC8wVd662A==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@smithy/eventstream-codec": "^3.1.9", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=10" } }, - "node_modules/@smithy/eventstream-serde-universal/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "tslib": "^2.6.2" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=8" } }, - "node_modules/@smithy/fetch-http-handler": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.8.tgz", - "integrity": "sha512-Lqe0B8F5RM7zkw//6avq1SJ8AfaRd3ubFUS1eVp5WszV7p6Ne5hQ4dSuMHDpNRPhgTvj4va9Kd/pcVigHEHRow==", - "dependencies": { - "@smithy/protocol-http": "^4.1.3", - "@smithy/querystring-builder": "^3.0.6", - "@smithy/types": "^3.4.2", - "@smithy/util-base64": "^3.0.0", - "tslib": "^2.6.2" + "node_modules/jose": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "funding": { + "url": "https://github.com/sponsors/panva" } }, - "node_modules/@smithy/fetch-http-handler/node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", - "dependencies": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", "engines": { - "node": ">=16.0.0" + "node": ">=10" } }, - "node_modules/@smithy/fetch-http-handler/node_modules/@smithy/querystring-builder": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.6.tgz", - "integrity": "sha512-sQe08RunoObe+Usujn9+R2zrLuQERi3CWvRO3BvnoWSYUaIrLKuAIeY7cMeDax6xGyfIP3x/yFWbEKSXvOnvVg==", + "node_modules/js-base64": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.5.tgz", + "integrity": "sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", "dependencies": { - "@smithy/types": "^3.4.2", - "@smithy/util-uri-escape": "^3.0.0", - "tslib": "^2.6.2" + "argparse": "^2.0.1" }, - "engines": { - "node": ">=16.0.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@smithy/fetch-http-handler/node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">= 10.16.0" } }, - "node_modules/@smithy/fetch-http-handler/node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "bignumber.js": "^9.0.0" } }, - "node_modules/@smithy/hash-blob-browser": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.9.tgz", - "integrity": "sha512-wOu78omaUuW5DE+PVWXiRKWRZLecARyP3xcq5SmkXUw9+utgN8HnSnBfrjL2B/4ZxgqPjaAJQkC/+JHf1ITVaQ==", + "node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "@smithy/chunked-blob-reader": "^4.0.0", - "@smithy/chunked-blob-reader-native": "^3.0.1", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "dequal": "^2.0.3" } }, - "node_modules/@smithy/hash-blob-browser/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/json-schema-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-3.0.0.tgz", + "integrity": "sha512-HqMnbz0tz2DaEJ3ntsqtx3ezzZyDE7G56A/pPY/NGmrPu76UzsWquOpHFRAf5beTNXoH2LU5cQePVvRli1nchA==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "debug": "^4.1.1", + "fast-uri": "^3.0.5", + "rfdc": "^1.1.4" }, "engines": { - "node": ">=16.0.0" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/Eomm/json-schema-resolver?sponsor=1" } }, - "node_modules/@smithy/hash-node": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.10.tgz", - "integrity": "sha512-3zWGWCHI+FlJ5WJwx73Mw2llYR8aflVyZN5JhoqLxbdPZi6UyKSdCeXAWJw9ja22m6S6Tzz1KZ+kAaSwvydi0g==", + "node_modules/json-schema-to-ts": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.0.0.tgz", + "integrity": "sha512-2adDesYifYEXYxNySx3gG0RR69rDWIjqAFzK/JPXdOvjHLZ/UP6d2rkpy6a+AxyhtRp2SvFPZ4+EW36jBinUbA==", + "dev": true, "dependencies": { - "@smithy/types": "^3.7.1", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" + "@babel/runtime": "^7.18.3", + "@types/json-schema": "^7.0.9", + "ts-algebra": "^1.2.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=16" } }, - "node_modules/@smithy/hash-node/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "dev": true, "dependencies": { - "tslib": "^2.6.2" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" }, "engines": { - "node": ">=16.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@smithy/hash-stream-node": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.9.tgz", - "integrity": "sha512-3XfHBjSP3oDWxLmlxnt+F+FqXpL3WlXs+XXaB6bV9Wo8BBu87fK1dSEsyH7Z4ZHRmwZ4g9lFMdf08m9hoX1iRA==", - "dependencies": { - "@smithy/types": "^3.7.1", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" }, "engines": { - "node": ">=16.0.0" + "node": ">=6" } }, - "node_modules/@smithy/hash-stream-node/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, "dependencies": { - "tslib": "^2.6.2" + "universalify": "^2.0.0" }, - "engines": { - "node": ">=16.0.0" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/@smithy/invalid-dependency": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.10.tgz", - "integrity": "sha512-Lp2L65vFi+cj0vFMu2obpPW69DU+6O5g3086lmI4XcnRCG8PxvpWC7XyaVwJCxsZFzueHjXnrOH/E0pl0zikfA==", - "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@smithy/invalid-dependency/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/jsonpath-plus": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz", + "integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", - "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", - "dependencies": { - "tslib": "^2.6.2" + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@smithy/md5-js": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.10.tgz", - "integrity": "sha512-m3bv6dApflt3fS2Y1PyWPUtRP7iuBlvikEOGwu0HsCZ0vE7zcIX+dBoh3e+31/rddagw8nj92j0kJg2TfV+SJA==", + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, "dependencies": { - "@smithy/types": "^3.7.1", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" + "graceful-fs": "^4.1.11" } }, - "node_modules/@smithy/md5-js/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/knex": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/knex/-/knex-3.1.0.tgz", + "integrity": "sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==", "dependencies": { - "tslib": "^2.6.2" + "colorette": "2.0.19", + "commander": "^10.0.0", + "debug": "4.3.4", + "escalade": "^3.1.1", + "esm": "^3.2.25", + "get-package-type": "^0.1.0", + "getopts": "2.3.0", + "interpret": "^2.2.0", + "lodash": "^4.17.21", + "pg-connection-string": "2.6.2", + "rechoir": "^0.8.0", + "resolve-from": "^5.0.0", + "tarn": "^3.0.2", + "tildify": "2.0.0" }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@smithy/middleware-content-length": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.12.tgz", - "integrity": "sha512-1mDEXqzM20yywaMDuf5o9ue8OkJ373lSPbaSjyEvkWdqELhFMyNNgKGWL/rCSf4KME8B+HlHKuR8u9kRj8HzEQ==", - "dependencies": { - "@smithy/protocol-http": "^4.1.7", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "bin": { + "knex": "bin/cli.js" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@smithy/middleware-content-length/node_modules/@smithy/protocol-http": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", - "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", - "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "node": ">=16" }, - "engines": { - "node": ">=16.0.0" + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "mysql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } } }, - "node_modules/@smithy/middleware-content-length/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "license": "MIT", "engines": { - "node": ">=16.0.0" + "node": ">=6" } }, - "node_modules/@smithy/middleware-endpoint": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.4.tgz", - "integrity": "sha512-TybiW2LA3kYVd3e+lWhINVu1o26KJbBwOpADnf0L4x/35vLVica77XVR5hvV9+kWeTGeSJ3IHTcYxbRxlbwhsg==", + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", "dependencies": { - "@smithy/core": "^2.5.4", - "@smithy/middleware-serde": "^3.0.10", - "@smithy/node-config-provider": "^3.1.11", - "@smithy/shared-ini-file-loader": "^3.1.11", - "@smithy/types": "^3.7.1", - "@smithy/url-parser": "^3.0.10", - "@smithy/util-middleware": "^3.0.10", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" } }, - "node_modules/@smithy/middleware-endpoint/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" }, - "node_modules/@smithy/middleware-retry": { - "version": "3.0.28", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.28.tgz", - "integrity": "sha512-vK2eDfvIXG1U64FEUhYxoZ1JSj4XFbYWkK36iz02i3pFwWiDz1Q7jKhGTBCwx/7KqJNk4VS7d7cDLXFOvP7M+g==", + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.11", - "@smithy/protocol-http": "^4.1.7", - "@smithy/service-error-classification": "^3.0.10", - "@smithy/smithy-client": "^3.4.5", - "@smithy/types": "^3.7.1", - "@smithy/util-middleware": "^3.0.10", - "@smithy/util-retry": "^3.0.10", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "detect-libc": "^2.0.3" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@smithy/middleware-retry/node_modules/@smithy/protocol-http": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", - "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", - "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "node": ">= 12.0.0" }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@smithy/middleware-retry/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dependencies": { - "tslib": "^2.6.2" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@smithy/middleware-serde": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.10.tgz", - "integrity": "sha512-MnAuhh+dD14F428ubSJuRnmRsfOpxSzvRhaGVTvd/lrUDE3kxzCCmH8lnVTvoNQnV2BbJ4c15QwZ3UdQBtFNZA==", - "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "node": ">= 12.0.0" }, - "engines": { - "node": ">=16.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@smithy/middleware-serde/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@smithy/middleware-stack": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.10.tgz", - "integrity": "sha512-grCHyoiARDBBGPyw2BeicpjgpsDFWZZxptbVKb3CRd/ZA15F/T6rZjCCuBUjJwdck1nwUuIxYtsS4H9DDpbP5w==", - "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "node": ">= 12.0.0" }, - "engines": { - "node": ">=16.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@smithy/middleware-stack/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@smithy/node-config-provider": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.11.tgz", - "integrity": "sha512-URq3gT3RpDikh/8MBJUB+QGZzfS7Bm6TQTqoh4CqE8NBuyPkWa5eUXj0XFcFfeZVgg3WMh1u19iaXn8FvvXxZw==", - "dependencies": { - "@smithy/property-provider": "^3.1.10", - "@smithy/shared-ini-file-loader": "^3.1.11", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@smithy/node-config-provider/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@smithy/node-http-handler": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.3.1.tgz", - "integrity": "sha512-gLA8qK2nL9J0Rk/WEZSvgin4AppvuCYRYg61dcUo/uKxvMZsMInL5I5ZdJTogOvdfVug3N2dgI5ffcUfS4S9PA==", - "dependencies": { - "@smithy/abort-controller": "^2.1.1", - "@smithy/protocol-http": "^3.1.1", - "@smithy/querystring-builder": "^2.1.1", - "@smithy/types": "^2.9.1", - "tslib": "^2.5.0" - }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@smithy/property-provider": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.10.tgz", - "integrity": "sha512-n1MJZGTorTH2DvyTVj+3wXnd4CzjJxyXeOgnTlgNVFxaaMeT4OteEp4QrzF8p9ee2yg42nvyVK6R/awLCakjeQ==", - "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@smithy/property-provider/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@smithy/protocol-http": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.1.1.tgz", - "integrity": "sha512-6ZRTSsaXuSL9++qEwH851hJjUA0OgXdQFCs+VDw4tGH256jQ3TjYY/i34N4vd24RV3nrjNsgd1yhb57uMoKbzQ==", - "dependencies": { - "@smithy/types": "^2.9.1", - "tslib": "^2.5.0" - }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@smithy/querystring-builder": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.1.1.tgz", - "integrity": "sha512-C/ko/CeEa8jdYE4gt6nHO5XDrlSJ3vdCG0ZAc6nD5ZIE7LBp0jCx4qoqp7eoutBu7VrGMXERSRoPqwi1WjCPbg==", - "dependencies": { - "@smithy/types": "^2.9.1", - "@smithy/util-uri-escape": "^2.1.1", - "tslib": "^2.5.0" - }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=14.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@smithy/querystring-parser": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.10.tgz", - "integrity": "sha512-Oa0XDcpo9SmjhiDD9ua2UyM3uU01ZTuIrNdZvzwUTykW1PM8o2yJvMh1Do1rY5sUQg4NDV70dMi0JhDx4GyxuQ==", - "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=16.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@smithy/querystring-parser/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" + }, + "node_modules/lodash._baseiteratee": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz", + "integrity": "sha512-nqB9M+wITz0BX/Q2xg6fQ8mLkyfF7MU7eE+MNBNjTHFKeKaZAPEzEg+E8LWxKWf1DQVflNEn9N49yAuqKh2mWQ==", + "dev": true, "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "lodash._stringtopath": "~4.8.0" } }, - "node_modules/@smithy/service-error-classification": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.10.tgz", - "integrity": "sha512-zHe642KCqDxXLuhs6xmHVgRwy078RfqxP2wRDpIyiF8EmsWXptMwnMwbVa50lw+WOGNrYm9zbaEg0oDe3PTtvQ==", - "dependencies": { - "@smithy/types": "^3.7.1" - }, - "engines": { - "node": ">=16.0.0" - } + "node_modules/lodash._basetostring": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz", + "integrity": "sha512-SwcRIbyxnN6CFEEK4K1y+zuApvWdpQdBHM/swxP962s8HIxPO3alBH5t3m/dl+f4CMUug6sJb7Pww8d13/9WSw==", + "dev": true }, - "node_modules/@smithy/service-error-classification/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/lodash._baseuniq": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", + "integrity": "sha512-Ja1YevpHZctlI5beLA7oc5KNDhGcPixFhcqSiORHNsp/1QTv7amAXzw+gu4YOvErqVlMVyIJGgtzeepCnnur0A==", + "dev": true, "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" } }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.11.tgz", - "integrity": "sha512-AUdrIZHFtUgmfSN4Gq9nHu3IkHMa1YDcN+s061Nfm+6pQ0mJy85YQDB0tZBCmls0Vuj22pLwDPmL92+Hvfwwlg==", - "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } + "node_modules/lodash._createset": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", + "integrity": "sha512-GTkC6YMprrJZCYU3zcqZj+jkXkrXzq3IPBcF/fIPpNEAB4hZEtXU8zp/RwKOvZl43NUmwDbyRk3+ZTbeRdEBXA==", + "dev": true }, - "node_modules/@smithy/shared-ini-file-loader/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } + "node_modules/lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ==", + "dev": true }, - "node_modules/@smithy/signature-v4": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.3.tgz", - "integrity": "sha512-pPSQQ2v2vu9vc8iew7sszLd0O09I5TRc5zhY71KA+Ao0xYazIG+uLeHbTJfIWGO3BGVLiXjUr3EEeCcEQLjpWQ==", + "node_modules/lodash._stringtopath": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz", + "integrity": "sha512-SXL66C731p0xPDC5LZg4wI5H+dJo/EO4KTqOMwLYCH3+FmmfAKJEZCm6ohGpI+T1xwsDsJCfL4OnhorllvlTPQ==", + "dev": true, "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.1.7", - "@smithy/types": "^3.7.1", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.10", - "@smithy/util-uri-escape": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "lodash._basetostring": "~4.12.0" } }, - "node_modules/@smithy/signature-v4/node_modules/@smithy/protocol-http": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", - "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "license": "MIT" + }, + "node_modules/lodash.uniqby": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz", + "integrity": "sha512-IRt7cfTtHy6f1aRVA5n7kT8rgN3N1nH6MOWLcHfpWG2SH19E3JksLK38MktLxZDhlAjCP9jpIXkOnRXlu6oByQ==", + "dev": true, "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "lodash._baseiteratee": "~4.7.0", + "lodash._baseuniq": "~4.6.0" } }, - "node_modules/@smithy/signature-v4/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/logflare-transport-core": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/logflare-transport-core/-/logflare-transport-core-0.4.1.tgz", + "integrity": "sha512-9q61GYsRSf4vZHXRvBClamFvPSRvfgWkDaBkYg4Q4DuofcpwikxPROB/h8rVX3xyE8jrKqKM0mAlrqCzJQS3MQ==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=16.0.0" + "node": "20 || >=22" } }, - "node_modules/@smithy/signature-v4/node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", "engines": { - "node": ">=16.0.0" + "node": ">=12" } }, - "node_modules/@smithy/smithy-client": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.5.tgz", - "integrity": "sha512-k0sybYT9zlP79sIKd1XGm4TmK0AS1nA2bzDHXx7m0nGi3RQ8dxxQUs4CPkSmQTKAo+KF9aINU3KzpGIpV7UoMw==", + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/core": "^2.5.4", - "@smithy/middleware-endpoint": "^3.2.4", - "@smithy/middleware-stack": "^3.0.10", - "@smithy/protocol-http": "^4.1.7", - "@smithy/types": "^3.7.1", - "@smithy/util-stream": "^3.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/@smithy/smithy-client/node_modules/@smithy/protocol-http": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", - "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" } }, - "node_modules/@smithy/smithy-client/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "semver": "^7.5.3" }, "engines": { - "node": ">=16.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/types": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.9.1.tgz", - "integrity": "sha512-vjXlKNXyprDYDuJ7UW5iobdmyDm6g8dDG+BFUncAg/3XJaN45Gy5RWWWUVgrzIK7S4R1KWgIX5LeJcfvSI24bw==", - "dependencies": { - "tslib": "^2.5.0" - }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">= 0.4" } }, - "node_modules/@smithy/url-parser": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.10.tgz", - "integrity": "sha512-j90NUalTSBR2NaZTuruEgavSdh8MLirf58LoGSk4AtQfyIymogIhgnGUU2Mga2bkMkpSoC9gxb74xBXL5afKAQ==", - "dependencies": { - "@smithy/querystring-parser": "^3.0.10", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" } }, - "node_modules/@smithy/url-parser/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=8.6" } }, - "node_modules/@smithy/util-base64": { + "node_modules/mime": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", - "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", - "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@smithy/util-body-length-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", - "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", - "dependencies": { - "tslib": "^2.6.2" + "node": ">=10.0.0" } }, - "node_modules/@smithy/util-body-length-node": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", - "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { - "node": ">=16.0.0" + "node": ">= 0.6" } }, - "node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", - "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "tslib": "^2.6.2" + "mime-db": "1.52.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 0.6" } }, - "node_modules/@smithy/util-config-provider": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", - "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.28", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.28.tgz", - "integrity": "sha512-6bzwAbZpHRFVJsOztmov5PGDmJYsbNSoIEfHSJJyFLzfBGCCChiO3od9k7E/TLgrCsIifdAbB9nqbVbyE7wRUw==", - "dependencies": { - "@smithy/property-provider": "^3.1.10", - "@smithy/smithy-client": "^3.4.5", - "@smithy/types": "^3.7.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">= 10.0.0" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/@smithy/util-defaults-mode-browser/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multistream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz", + "integrity": "sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "once": "^1.4.0", + "readable-stream": "^3.6.0" } }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.28", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.28.tgz", - "integrity": "sha512-78ENJDorV1CjOQselGmm3+z7Yqjj5HWCbjzh0Ixuq736dh1oEnD9sAttSBNSLlpZsX8VQnmERqA2fEFlmqWn8w==", - "dependencies": { - "@smithy/config-resolver": "^3.0.12", - "@smithy/credential-provider-imds": "^3.2.7", - "@smithy/node-config-provider": "^3.1.11", - "@smithy/property-provider": "^3.1.10", - "@smithy/smithy-client": "^3.4.5", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">= 10.0.0" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/@smithy/util-defaults-mode-node/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "engines": { - "node": ">=16.0.0" + "node": ">= 0.6" } }, - "node_modules/@smithy/util-endpoints": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.6.tgz", - "integrity": "sha512-mFV1t3ndBh0yZOJgWxO9J/4cHZVn5UG1D8DeCc6/echfNkeEJWu9LD7mgGH5fHrEdR7LDoWw7PQO6QiGpHXhgA==", - "dependencies": { - "@smithy/node-config-provider": "^3.1.11", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], "engines": { - "node": ">=16.0.0" + "node": ">=10.5.0" } }, - "node_modules/@smithy/util-endpoints/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dependencies": { - "tslib": "^2.6.2" + "whatwg-url": "^5.0.0" }, "engines": { - "node": ">=16.0.0" + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/@smithy/util-hex-encoding": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", - "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "node_modules/node-gyp-build": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.9.0.tgz", + "integrity": "sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" } }, - "node_modules/@smithy/util-middleware": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.10.tgz", - "integrity": "sha512-eJO+/+RsrG2RpmY68jZdwQtnfsxjmPxzMlQpnHKjFPwrYqvlcT+fHdT+ZVwcjlWSrByOhGr9Ff2GG17efc192A==", - "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, + "node_modules/oauth4webapi": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.1.tgz", + "integrity": "sha512-olkZDELNycOWQf9LrsELFq8n05LwJgV8UkrS0cburk6FOwf8GvLam+YB+Uj5Qvryee+vwWOfQVeI5Vm0MVg7SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, "engines": { - "node": ">=16.0.0" + "node": ">= 0.4" } }, - "node_modules/@smithy/util-middleware/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/object-sizeof": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/object-sizeof/-/object-sizeof-2.6.4.tgz", + "integrity": "sha512-YuJAf7Bi61KROcYmXm8RCeBrBw8UOaJDzTm1gp0eU7RjYi1xEte3/Nmg/VyPaHcJZ3sNojs1Y0xvSrgwkLmcFw==", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "buffer": "^6.0.3" } }, - "node_modules/@smithy/util-retry": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.10.tgz", - "integrity": "sha512-1l4qatFp4PiU6j7UsbasUHL2VU023NRB/gfaa1M0rDqVrRN4g3mCArLRyH3OuktApA4ye+yjWQHjdziunw2eWA==", + "node_modules/object-sizeof/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "@smithy/service-error-classification": "^3.0.10", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/@smithy/util-retry/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", + "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "wrappy": "1" } }, - "node_modules/@smithy/util-stream": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.3.1.tgz", - "integrity": "sha512-Ff68R5lJh2zj+AUTvbAU/4yx+6QPRzg7+pI7M1FbtQHcRIp7xvguxVsQBKyB3fwiOwhAKu0lnNyYBaQfSW6TNw==", - "dependencies": { - "@smithy/fetch-http-handler": "^4.1.1", - "@smithy/node-http-handler": "^3.3.1", - "@smithy/types": "^3.7.1", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@smithy/util-stream/node_modules/@smithy/abort-controller": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.8.tgz", - "integrity": "sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==", + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT" + }, + "node_modules/openid-client": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.0.tgz", + "integrity": "sha512-oG1d1nAVhIIE+JSjLS+7E9wY1QOJpZltkzlJdbZ7kEn7Hp3hqur2TEeQ8gLOHoHkhbRAGZJKoOnEQcLOQJuIyg==", + "license": "MIT", "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "jose": "^6.1.0", + "oauth4webapi": "^3.8.1" }, - "engines": { - "node": ">=16.0.0" + "funding": { + "url": "https://github.com/sponsors/panva" } }, - "node_modules/@smithy/util-stream/node_modules/@smithy/fetch-http-handler": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.1.tgz", - "integrity": "sha512-bH7QW0+JdX0bPBadXt8GwMof/jz0H28I84hU1Uet9ISpzUqXqRQ3fEZJ+ANPOhzSEczYvANNl3uDQDYArSFDtA==", + "node_modules/otlp-logger": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/otlp-logger/-/otlp-logger-1.1.13.tgz", + "integrity": "sha512-r53tPnMLprtQSMOJUkj4Az4tR8NL+U+8C7M8BV1ZA9y7cDfAbWQp2mRL/eYS/O786oAi9KnN9hKsZ5cFKNchKw==", + "license": "MIT", "dependencies": { - "@smithy/protocol-http": "^4.1.7", - "@smithy/querystring-builder": "^3.0.10", - "@smithy/types": "^3.7.1", - "@smithy/util-base64": "^3.0.0", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "^0.206.0", + "@opentelemetry/exporter-logs-otlp-grpc": "^0.206.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.206.0", + "@opentelemetry/exporter-logs-otlp-proto": "^0.206.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/sdk-logs": "^0.206.0" } }, - "node_modules/@smithy/util-stream/node_modules/@smithy/node-http-handler": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.1.tgz", - "integrity": "sha512-fr+UAOMGWh6bn4YSEezBCpJn9Ukp9oR4D32sCjCo7U81evE11YePOQ58ogzyfgmjIO79YeOdfXXqr0jyhPQeMg==", + "node_modules/otlp-logger/node_modules/@opentelemetry/api-logs": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.206.0.tgz", + "integrity": "sha512-yIVDu9jX//nV5wSMLZLdHdb1SKHIMj9k+wQVFtln5Flcgdldz9BkHtavvExQiJqBZg2OpEEJEZmzQazYztdz2A==", + "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^3.1.8", - "@smithy/protocol-http": "^4.1.7", - "@smithy/querystring-builder": "^3.0.10", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=8.0.0" } }, - "node_modules/@smithy/util-stream/node_modules/@smithy/protocol-http": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", - "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", + "node_modules/otlp-logger/node_modules/@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@smithy/util-stream/node_modules/@smithy/querystring-builder": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.10.tgz", - "integrity": "sha512-nT9CQF3EIJtIUepXQuBFb8dxJi3WVZS3XfuDksxSCSn+/CzZowRLdhDn+2acbBv8R6eaJqPupoI/aRFIImNVPQ==", + "node_modules/otlp-logger/node_modules/@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.206.0.tgz", + "integrity": "sha512-kJKxKBaGwqWop95d6tcluz260IWwIgOG0BH8oVm6429tg8LxY2PJb7Om8d5s+5vOFM8DkUYCnIpn9d/13/RcKQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.7.1", - "@smithy/util-uri-escape": "^3.0.0", - "tslib": "^2.6.2" + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.206.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.206.0", + "@opentelemetry/otlp-transformer": "0.206.0", + "@opentelemetry/sdk-logs": "0.206.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@smithy/util-stream/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/otlp-logger/node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.206.0.tgz", + "integrity": "sha512-VWcHEnS+1kN+sQTAdCgSn2anqHPxY1/e52fhpe2mcSnEaXI1srFf3RU5DAu7hzQO6T9DPQzOKG8kc76vCtyYDw==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.206.0", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.206.0", + "@opentelemetry/otlp-transformer": "0.206.0", + "@opentelemetry/sdk-logs": "0.206.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@smithy/util-stream/node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "node_modules/otlp-logger/node_modules/@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.206.0.tgz", + "integrity": "sha512-CsYNXJwkn1qCXJGE+/JvvYucAjL8rpaxa2hnl+tDP6M5E0O3UVa8zG4ZUEebjr5J5Nc32egvslEZx5rgNOp3lQ==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.206.0", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.206.0", + "@opentelemetry/otlp-transformer": "0.206.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/sdk-logs": "0.206.0", + "@opentelemetry/sdk-trace-base": "2.1.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@smithy/util-uri-escape": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.1.1.tgz", - "integrity": "sha512-saVzI1h6iRBUVSqtnlOnc9ssU09ypo7n+shdQ8hBTZno/9rZ3AuRYvoHInV57VF7Qn7B+pFJG7qTzFiHxWlWBw==", + "node_modules/otlp-logger/node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.206.0.tgz", + "integrity": "sha512-Rv54oSNKMHYS5hv+H5EGksfBUtvPQWFTK+Dk6MjJun9tOijCsFJrhRFvAqg5d67TWSMn+ZQYRKIeXh5oLVrpAQ==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-transformer": "0.206.0" }, "engines": { - "node": ">=14.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", - "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "node_modules/otlp-logger/node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.206.0.tgz", + "integrity": "sha512-IA8EDbrB8OKtidMqErBY8sUc9mh03LOXuNPwp4/rdPrxSt45g1gBuZMovRXdEWfRyKKbF2E7MdipT2m11bs6SQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "tslib": "^2.6.2" + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.206.0", + "@opentelemetry/otlp-transformer": "0.206.0" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@smithy/util-waiter": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.9.tgz", - "integrity": "sha512-/aMXPANhMOlMPjfPtSrDfPeVP8l56SJlz93xeiLmhLe5xvlXA5T3abZ2ilEsDEPeY9T/wnN/vNGn9wa1SbufWA==", - "dependencies": { - "@smithy/abort-controller": "^3.1.8", - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "node": "^18.19.0 || >=20.6.0" }, - "engines": { - "node": ">=16.0.0" + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@smithy/util-waiter/node_modules/@smithy/abort-controller": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.8.tgz", - "integrity": "sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==", + "node_modules/otlp-logger/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.206.0.tgz", + "integrity": "sha512-Li2Cik1WnmNbU2mmTnw7DxvRiXhMcnAuTfAclP8y/zy7h5+GrLDpTZ+Z0XUs+Q3MLkb/h3ry4uFrC/z+2a6X7g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" + "@opentelemetry/api-logs": "0.206.0", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/sdk-logs": "0.206.0", + "@opentelemetry/sdk-metrics": "2.1.0", + "@opentelemetry/sdk-trace-base": "2.1.0", + "protobufjs": "^7.3.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "node_modules/@smithy/util-waiter/node_modules/@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "node_modules/otlp-logger/node_modules/@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=16.0.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@tus/file-store": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tus/file-store/-/file-store-2.0.0.tgz", - "integrity": "sha512-LTh9L/RoWoo2TbBGPZOuhuyEIIqweoTekT77ZkIVkpYkLK8zTt++PRdY+VyJsLDbFMO9RzvKSBRmj1H8SPdDew==", + "node_modules/otlp-logger/node_modules/@opentelemetry/sdk-logs": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.206.0.tgz", + "integrity": "sha512-SQ2yTmqe4Mw9RI3a/glVkfjWPsXh6LySvnljXubiZq4zu+UP8NMJt2j82ZsYb+KpD7Eu+/41/7qlJnjdeVjz7Q==", + "license": "Apache-2.0", "dependencies": { - "@tus/utils": "^0.6.0", - "debug": "^4.3.4" + "@opentelemetry/api-logs": "0.206.0", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0" }, "engines": { - "node": ">=20.19.0" + "node": "^18.19.0 || >=20.6.0" }, - "optionalDependencies": { - "@redis/client": "^1.6.0" + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/@tus/s3-store": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tus/s3-store/-/s3-store-2.0.1.tgz", - "integrity": "sha512-V4hHtQgUWQMVFJJIVRkCrCoSxpDeYrCnqQKf65qYC5fSb5suBmYAApxYJ7AnIkhX6fscx1Lkc86a1+C3a0k/Jg==", - "license": "MIT", + "node_modules/otlp-logger/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.1.0.tgz", + "integrity": "sha512-J9QX459mzqHLL9Y6FZ4wQPRZG4TOpMCyPOh6mkr/humxE1W2S3Bvf4i75yiMW9uyed2Kf5rxmLhTm/UK8vNkAw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-s3": "^3.758.0", - "@shopify/semaphore": "^3.1.0", - "@tus/utils": "^0.6.0", - "debug": "^4.3.4", - "multistream": "^4.1.0" + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0" }, "engines": { - "node": ">=20.19.0" + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, - "node_modules/@tus/server": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@tus/server/-/server-2.2.1.tgz", - "integrity": "sha512-VLf74cpRsk1H3UdAs3mwBfWsw5aI0bUYKpkzjVhdw/yJGZlcOGLxz0FdmkgpIvW+8tBHulMog+v6dg5rCAmIAw==", + "node_modules/otlp-logger/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", + "license": "Apache-2.0", "dependencies": { - "@tus/utils": "^0.6.0", - "debug": "^4.3.4", - "lodash.throttle": "^4.1.1", - "set-cookie-parser": "^2.7.1", - "srvx": "^0.2.8" + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=20.19.0" + "node": "^18.19.0 || >=20.6.0" }, - "optionalDependencies": { - "@redis/client": "^1.6.0", - "ioredis": "^5.4.1" + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, - "node_modules/@tus/utils": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@tus/utils/-/utils-0.6.0.tgz", - "integrity": "sha512-GpMpAQfVdC4UDhpsZrRPjGpdXg+JW5MquqMqtObUVsORwLBV6XI67iTT5be+z98THdqb6dl3bTLIElIdgPeo2g==", + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/async-retry": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@types/async-retry/-/async-retry-1.4.5.tgz", - "integrity": "sha512-YrdjSD+yQv7h6d5Ip+PMxh3H6ZxKyQk0Ts+PvaNRInxneG9PFVZjFg77ILAN+N6qYf7g4giSJ1l+ZjQ1zeegvA==", + "node_modules/patch-package": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", + "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", "dev": true, "dependencies": { - "@types/retry": "*" + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^10.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.2.4", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" } }, - "node_modules/@types/aws-lambda": { - "version": "8.10.143", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.143.tgz", - "integrity": "sha512-u5vzlcR14ge/4pMTTMDQr3MF0wEe38B2F9o84uC4F43vN5DGTy63npRrB6jQhyt+C0lGv4ZfiRcRkqJoZuPnmg==" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "engines": { + "node": ">=6" } }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, + "node_modules/path-expression-matcher": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.1.tgz", + "integrity": "sha512-d7gQQmLvAKXKXE2GeP9apIGbMYKz88zWdsn/BN2HRWVQsDFdUY36WSLTY0Jvd4HWi7Fb30gQ62oAOzdgJA6fZw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" } }, - "node_modules/@types/babel__traverse": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.2.tgz", - "integrity": "sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, - "node_modules/@types/bunyan": { - "version": "1.8.9", - "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.9.tgz", - "integrity": "sha512-ZqS9JGpBxVOvsawzmVt30sP++gSQMTejCkIAQ3VdadOcRE8izTyW66hufvwLeH+YEGP6Js2AW7Gz+RMyvrEbmw==", + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", "dependencies": { - "@types/node": "*" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@types/busboy": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.3.0.tgz", - "integrity": "sha512-Qx7ehfGO/k2yiTVpRIVIu16oVgbJpG65WLjEhbNSoTPdQEoRCorFUKHsSznyHmIkdvzOg2W3JWeewCmfSwcgeA==", + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "dependencies": { - "@types/node": "*" + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } } }, - "node_modules/@types/connect": { - "version": "3.4.36", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", - "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", + "node_modules/pg-boss": { + "version": "10.3.2", + "resolved": "git+ssh://git@github.com/supabase/pg-boss.git#89b675b057ff7cec3ca2c434e4d3005c35a76bd9", + "license": "MIT", "dependencies": { - "@types/node": "*" + "cron-parser": "^4.9.0", + "pg": "^8.16.0", + "serialize-error": "^8.1.0" + }, + "engines": { + "node": ">=20" } }, - "node_modules/@types/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==", - "dev": true + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "optional": true }, - "node_modules/@types/fs-extra": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", - "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", - "dev": true, - "dependencies": { - "@types/node": "*" + "node_modules/pg-connection-string": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", + "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==" + }, + "node_modules/pg-format": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pg-format/-/pg-format-1.0.4.tgz", + "integrity": "sha1-J3NCNsKtP05QZJFaWTNOIAQKgo4=", + "engines": { + "node": ">=4.0" } }, - "node_modules/@types/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", - "dev": true, - "dependencies": { - "@types/minimatch": "^5.1.2", - "@types/node": "*" + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" } }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, + "node_modules/pg-listen": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/pg-listen/-/pg-listen-1.7.0.tgz", + "integrity": "sha512-MKDwKLm4ryhy7iq1yw1K1MvUzBdTkaT16HZToddX9QaT8XSdt3Kins5mYH6DLECGFzFWG09VdXvWOIYogjXrsg==", "dependencies": { - "@types/node": "*" + "debug": "^4.1.1", + "pg-format": "^1.0.4", + "typed-emitter": "^0.1.0" + }, + "peerDependencies": { + "pg": "7.x || 8.x" } }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "peerDependencies": { + "pg": ">=8.0" + } }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", "dependencies": { - "@types/istanbul-lib-coverage": "*" + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, + "node_modules/pg/node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==" + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", "dependencies": { - "@types/istanbul-lib-report": "*" + "split2": "^4.1.0" } }, - "node_modules/@types/jest": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.1.tgz", - "integrity": "sha512-nKixEdnGDqFOZkMTF74avFNr3yRqB1ZJ6sRZv5/28D5x2oLN14KApv7F9mfDT/vUic0L3tRCsh3XWpWjtJisUQ==", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } + "license": "ISC" }, - "node_modules/@types/js-yaml": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", - "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", - "dev": true - }, - "node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, - "node_modules/@types/memcached": { - "version": "2.2.10", - "resolved": "https://registry.npmjs.org/@types/memcached/-/memcached-2.2.10.tgz", - "integrity": "sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg==", + "node_modules/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "license": "MIT", "dependencies": { - "@types/node": "*" + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" } }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true - }, - "node_modules/@types/multistream": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@types/multistream/-/multistream-4.1.3.tgz", - "integrity": "sha512-t57vmDEJOZuC0M3IrZYfCd9wolTcr3ZTCGk1iwHNosvgBX+7/SMvCGcR8wP9lidpelBZQ12crSuINOxkk0azPA==", - "dev": true, + "node_modules/pino-abstract-transport": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz", + "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==", "dependencies": { - "@types/node": "*" + "readable-stream": "^4.0.0", + "split2": "^4.0.0" } }, - "node_modules/@types/mustache": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/mustache/-/mustache-4.2.2.tgz", - "integrity": "sha512-MUSpfpW0yZbTgjekDbH0shMYBUD+X/uJJJMm9LXN1d5yjl5lCY1vN/eWKD6D1tOtjA6206K0zcIPnUaFMurdNA==", - "dev": true - }, - "node_modules/@types/mysql": { - "version": "2.15.26", - "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.26.tgz", - "integrity": "sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==", + "node_modules/pino-abstract-transport/node_modules/readable-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.1.0.tgz", + "integrity": "sha512-sVisi3+P2lJ2t0BPbpK629j8wRW06yKGJUcaLAGXPAUhyUxVJm7VsCTit1PFgT4JHUDMrGNR+ZjSKpzGaRF3zw==", "dependencies": { - "@types/node": "*" + "abort-controller": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@types/node": { - "version": "20.11.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.18.tgz", - "integrity": "sha512-ABT5VWnnYneSBcNWYSCuR05M826RoMyMSGiFivXGx6ZUIsXb9vn4643IEwkg2zbEOSgAiSogtapN2fgc4mAPlw==", + "node_modules/pino-logflare": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/pino-logflare/-/pino-logflare-0.5.2.tgz", + "integrity": "sha512-iRq4GLQPHb5FsTa8ZQOP+Z1w7jSl8cxkJRX5Q3oYlPLQmBVEclHB0wQ9qT2hHExlycBWZ2Pz8KnijPstpBY9jg==", + "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "batch2": "^1.0.6", + "commander": "^5.0.0", + "fast-json-parse": "^1.0.3", + "logflare-transport-core": "^0.4.1", + "pino-abstract-transport": "^1.0.0", + "pumpify": "^2.0.1", + "split2": "^3.1.1", + "through2": "^3.0.1" + }, + "bin": { + "pino-logflare": "dist/cli.js" + }, + "peerDependencies": { + "pino": "*" } }, - "node_modules/@types/pg": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.4.tgz", - "integrity": "sha512-uYA7UMVzDFpJobCrqwW/iWkFmvizy6knIUgr0Quaw7K1Le3ZnF7hI3bKqFoxPZ+fju1Sc7zdTvOl9YfFZPcmeA==", - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" + "node_modules/pino-logflare/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "engines": { + "node": ">= 6" } }, - "node_modules/@types/pg-pool": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz", - "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==", + "node_modules/pino-logflare/node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", "dependencies": { - "@types/pg": "*" + "readable-stream": "^3.0.0" } }, - "node_modules/@types/retry": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", - "dev": true - }, - "node_modules/@types/shimmer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", - "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==" - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/stream-buffers": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.7.tgz", - "integrity": "sha512-azOCy05sXVXrO+qklf0c/B07H/oHaIuDDAiHPVwlk3A9Ek+ksHyTeMajLZl3r76FxpPpxem//4Te61G1iW3Giw==", - "dev": true, + "node_modules/pino-opentelemetry-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-opentelemetry-transport/-/pino-opentelemetry-transport-2.0.0.tgz", + "integrity": "sha512-tWoq02WEtnCWfr63Co1n0sGlDpkBz2YUovSAsSBv/+jwKUIn/PjxwRileGViKrH/K3e+oc71nGl6yUdgQlrVkg==", + "license": "MIT", "dependencies": { - "@types/node": "*" + "otlp-logger": "^1.1.4", + "pino-abstract-transport": "^3.0.0" + }, + "peerDependencies": { + "pino": "^10.0.0" } }, - "node_modules/@types/strip-bom": { + "node_modules/pino-opentelemetry-transport/node_modules/pino-abstract-transport": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", - "dev": true - }, - "node_modules/@types/strip-json-comments": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", - "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", - "dev": true - }, - "node_modules/@types/tedious": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", - "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", "dependencies": { - "@types/node": "*" + "split2": "^4.0.0" } }, - "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/xml2js": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", - "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==", - "dev": true, + "node_modules/pino-pretty": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz", + "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", + "license": "MIT", "dependencies": { - "@types/node": "*" + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^4.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pump": "^3.0.0", + "secure-json-parse": "^4.0.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^5.0.2" + }, + "bin": { + "pino-pretty": "bin.js" } }, - "node_modules/@types/yargs": { - "version": "17.0.13", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz", - "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==", - "dev": true, + "node_modules/pino-pretty/node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "split2": "^4.0.0" } }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz", - "integrity": "sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.7.0", - "@typescript-eslint/type-utils": "8.7.0", - "@typescript-eslint/utils": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" - }, + "node_modules/pino-pretty/node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=14.16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/parser": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz", - "integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==", - "dev": true, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, + "node_modules/pino/node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.7.0", - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/typescript-estree": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "split2": "^4.0.0" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz", - "integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==", - "dev": true, + "node_modules/platformatic": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/platformatic/-/platformatic-3.52.0.tgz", + "integrity": "sha512-GqWwRgtgIudyELh0QhZ9yf602vBgmrv7pOITskVIPALE9dLfZzTCWq9b+d8vVsAEaBRVMlxx7I8m4be+AP9yzw==", + "license": "Apache-2.0", "dependencies": { - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0" + "@platformatic/foundation": "3.52.0", + "wattpm": "3.52.0" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "bin": { + "platformatic": "bin/platformatic.js", + "plt": "bin/plt.js" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "engines": { + "node": ">=22.19.0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz", - "integrity": "sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==", + "node_modules/postcss": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.7.0", - "@typescript-eslint/utils": "8.7.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": "^10 || ^12 || >=14" } }, - "node_modules/@typescript-eslint/types": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz", - "integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==", - "dev": true, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=4" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz", - "integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/@typescript-eslint/typescript-estree/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/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/@typescript-eslint/typescript-estree/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, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "dependencies": { - "brace-expansion": "^2.0.1" + "xtend": "^4.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=0.10.0" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz", - "integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==", - "dev": true, + "node_modules/postgres-migrations": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/postgres-migrations/-/postgres-migrations-5.3.0.tgz", + "integrity": "sha512-gnTHWJZVWbW8T3mXIxJm1JRU853TqBVWkhgfsTJr7zqT3VexjRmJj9kNi96rVhfTezDU4FVW6pf701kLOZiKIA==", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.7.0", - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/typescript-estree": "8.7.0" + "pg": "^8.6.0", + "sql-template-strings": "^2.2.2" + }, + "bin": { + "pg-validate-migrations": "dist/bin/validate.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">10.17.0" + } + }, + "node_modules/pprof-format": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/pprof-format/-/pprof-format-2.2.1.tgz", + "integrity": "sha512-p4tVN7iK19ccDqQv8heyobzUmbHyds4N2FI6aBMcXz6y99MglTWDxIyhFkNaLeEXs6IFUEzT0zya0icbSLLY0g==" + }, + "node_modules/pprof-to-md": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/pprof-to-md/-/pprof-to-md-0.1.0.tgz", + "integrity": "sha512-2OnF8vTBT37lfaDwIK9xJHGl6Sl05mQpoFjnqLOrLw0XTSN8tj6nVlwIOSAi67Ys57/qZpyHvqzpQlw36bH4kg==", + "license": "Apache-2.0", + "dependencies": { + "pprof-format": "^2.1.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "bin": { + "pprof-to-md": "dist/cli.js" }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "engines": { + "node": ">=22.6.0" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz", - "integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==", + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.7.0", - "eslint-visitor-keys": "^3.4.3" + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=10.13.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "license": "MIT", "dependencies": { - "event-target-shim": "^5.0.0" + "parse-ms": "^4.0.0" }, "engines": { - "node": ">=6.5" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/abstract-logging": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", - "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/prom-client": { + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", + "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", + "license": "Apache-2.0", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "bin": { - "acorn": "bin/acorn" + "@opentelemetry/api": "^1.4.0", + "tdigest": "^0.1.1" }, "engines": { - "node": ">=0.4.0" + "node": "^16 || ^18 || >=20" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "peerDependencies": { - "acorn": "^8" + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "engines": { + "node": ">= 4" } }, - "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, "dependencies": { - "debug": "^4.3.4" + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" }, "engines": { - "node": ">= 14" + "node": ">=12.0.0" } }, - "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "node_modules/pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, + "node_modules/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" + "scheduler": "^0.27.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "react": "^19.2.5" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" + "node_modules/react-pprof": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/react-pprof/-/react-pprof-1.5.1.tgz", + "integrity": "sha512-IOdUPcVjBMahl9Uw+TnE2CKybfVjiNd1mKjHMipJInezH8XdDRL5b67G5wzJkrii0L/Xj+LmESMm5d1gPIgv7Q==", + "license": "Apache-2.0", + "dependencies": { + "gl-matrix": "^3.4.3", + "pprof-format": "^2.2.1", + "protobufjs": "^7.5.3", + "react": "^19.1.0", + "react-dom": "^19.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "react-pprof": "cli.js" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 12.13.0" } }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "resolve": "^1.20.0" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==" - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, - "node_modules/async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", - "dependencies": { - "retry": "0.13.1" + "node": ">= 10.13.0" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/avvio": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.1.0.tgz", - "integrity": "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==", - "license": "MIT", - "dependencies": { - "@fastify/error": "^4.0.0", - "fastq": "^1.17.1" + "node": ">=4" } }, - "node_modules/aws-sigv4-sign": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/aws-sigv4-sign/-/aws-sigv4-sign-1.2.1.tgz", - "integrity": "sha512-iS0pV4xGzhexBCMG9ggXM5CaxTHa3KxOxkw2tphLgA/60vSycSWjJWso0s4xzGRrtABSi0b3LlxG5Jek7NjuqA==", + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-sdk/credential-provider-node": "^3.609.0", - "@smithy/protocol-http": "^4.0.3", - "@smithy/signature-v4": "^3.1.2" + "redis-errors": "^1.0.0" }, "engines": { - "node": ">=18" + "node": ">=4" } }, - "node_modules/aws-sigv4-sign/node_modules/@smithy/protocol-http": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", - "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", - "dependencies": { - "@smithy/types": "^3.7.2", - "tslib": "^2.6.2" - }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "engines": { - "node": ">=16.0.0" + "node": ">=0.10.0" } }, - "node_modules/aws-sigv4-sign/node_modules/@smithy/signature-v4": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-3.1.2.tgz", - "integrity": "sha512-3BcPylEsYtD0esM4Hoyml/+s7WP2LFhcM3J2AGdcL2vx9O60TtfpDOL72gjb4lU8NeRPeKAwR77YNyyGvMbuEA==", - "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "@smithy/types": "^3.3.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-uri-escape": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "engines": { - "node": ">=16.0.0" + "node": ">=0.10.0" } }, - "node_modules/aws-sigv4-sign/node_modules/@smithy/types": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", - "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", "dependencies": { - "tslib": "^2.6.2" + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" }, "engines": { - "node": ">=16.0.0" + "node": ">=9.3.0 || >=8.10.0 <9.0.0" } }, - "node_modules/aws-sigv4-sign/node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "node_modules/require-in-the-middle/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dependencies": { - "tslib": "^2.6.2" + "ms": "^2.1.3" }, "engines": { - "node": ">=16.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/axios": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.1.tgz", - "integrity": "sha512-Kn4kbSXpkFHCGE6rBFNwIv0GQs4AvDT80jlveJDKFxjbTYMUeB4QtsdPCv6H8Cm19Je7IU6VFtRl2zWZI0rudQ==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } + "node_modules/require-in-the-middle/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/axios-retry": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.9.1.tgz", - "integrity": "sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w==", - "dependencies": { - "@babel/runtime": "^7.15.4", - "is-retry-allowed": "^2.2.0" - } + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "license": "MIT", + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "bin": { + "resolve": "bin/resolve" }, - "peerDependencies": { - "@babel/core": "^7.8.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", - "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "node_modules/resolve-tspaths": { + "version": "0.8.19", + "resolved": "https://registry.npmjs.org/resolve-tspaths/-/resolve-tspaths-0.8.19.tgz", + "integrity": "sha512-yZkXNYyHdVytOkJLhbib7TFpaMVElk9auO9R1jDmOSXPUWEjy+V44VqX77RIQ+kf0UJIlAGRDK/yrbfwlu1UWg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.4", - "semver": "^6.3.1" + "ansi-colors": "4.1.3", + "commander": "12.0.0", + "fast-glob": "3.3.2" + }, + "bin": { + "resolve-tspaths": "dist/main.js" }, "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "typescript": ">=3.0.3" } }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/resolve-tspaths/node_modules/commander": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", + "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "engines": { + "node": ">=18" } }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", - "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", - "dev": true, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3", - "core-js-compat": "^3.40.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "engines": { + "node": ">=10" } }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", - "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.4" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" } }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" } }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "node_modules/rfc4648": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.4.tgz", + "integrity": "sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==", + "license": "MIT" + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" + }, + "bin": { + "rolldown": "bin/cli.mjs" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^20.19.0 || >=22.12.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -15767,211 +15565,203 @@ "type": "consulting", "url": "https://feross.org/support" } - ] - }, - "node_modules/batch2": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/batch2/-/batch2-1.0.6.tgz", - "integrity": "sha512-xZsZx73HfBcoUMITZwqRF+gO5RGx5Sf+hZjmxoRuu8xpYM003aSDM7uwKKbmATJG1Kuc5Rs0kck/0ALpOnue0w==", + ], "dependencies": { - "through2": "^3.0.1" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/bignumber.js": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", - "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" + "queue-microtask": "^1.2.2" } }, - "node_modules/bintrees": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", - "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==" - }, - "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==" + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "node_modules/safe-regex2": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", + "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "ret": "~0.5.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, + "node_modules/safe-stable-stringify": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", + "integrity": "sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==", "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", - "dev": true, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" + "type": "github", + "url": "https://github.com/sponsors/fastify" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "opencollective", + "url": "https://opencollective.com/fastify" } ], - "license": "MIT", + "license": "BSD-3-Clause" + }, + "node_modules/semgrator": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/semgrator/-/semgrator-0.3.0.tgz", + "integrity": "sha512-TIMBco3kY4+jNk+uiSpbW6dwZ2kCnLPEcPbxIpcDV9UcVL0egYsiQIhljZU5meLTYjNRqFyZ+JwdsfC4ryrUCA==", + "license": "Apache-2.0", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" - }, + "abstract-logging": "^2.0.1", + "rfdc": "^1.3.1", + "semver": "^7.6.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", "bin": { - "browserslist": "cli.js" + "semver": "bin/semver.js" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">=10" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, + "node_modules/serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", "dependencies": { - "fast-json-stable-stringify": "2.x" + "type-fest": "^0.20.2" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "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==", - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "dependencies": { + "define-data-property": "^1.1.4", "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001715", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz", - "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==", + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" + "license": "ISC" }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/chalk/node_modules/ansi-styles": { + "node_modules/slice-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -15982,14040 +15772,3013 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/chalk/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", "engines": { - "node": ">=7.0.0" + "node": ">= 6.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/chalk/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, "engines": { - "node": ">=10" + "node": ">= 10.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" }, "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": ">= 14" } }, - "node_modules/ci-info": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz", - "integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==", - "dev": true - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", - "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" + "atomic-sleep": "^1.0.0" } }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" + "node": ">= 10.x" } }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true, + "node_modules/sponge-case": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sponge-case/-/sponge-case-2.0.3.tgz", + "integrity": "sha512-i4h9ZGRfxV6Xw3mpZSFOfbXjf0cQcYmssGWutgNIfFZ2VM+YIWfD71N/kjjwK6X/AAHzBr+rciEcn/L34S8TGw==", "license": "MIT" }, - "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" - }, - "node_modules/combine-errors": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/combine-errors/-/combine-errors-3.0.3.tgz", - "integrity": "sha512-C8ikRNRMygCwaTx+Ek3Yr+OuZzgZjduCOfSQBjbM8V3MfgcjSTeto/GXP6PAwKvJz/v15b7GHZvx5rOlczFw/Q==", - "dev": true, - "dependencies": { - "custom-error-instance": "2.1.1", - "lodash.uniqby": "4.5.0" + "node_modules/sql-template-strings": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/sql-template-strings/-/sql-template-strings-2.2.2.tgz", + "integrity": "sha1-PxFQiiWt384hejBCqdMAwxk7lv8=", + "engines": { + "node": ">=4.0.0" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/srvx": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.2.8.tgz", + "integrity": "sha512-9HG8eqhHLsvzhW4nJTg/4Sob1Sw/fB6S8Yzvf+iUD6l5CT4D0CXfPcFfh1r2eJFNHC0JOezK+YrgoufA5goYMQ==", "dependencies": { - "delayed-stream": "~1.0.0" + "cookie-es": "^2.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=20.11.1" } }, - "node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">= 0.8" } }, - "node_modules/compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, + "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==", "dependencies": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/connection-string": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/connection-string/-/connection-string-4.3.6.tgz", - "integrity": "sha512-dwaq4BMeiIIWry/oQxjstPB7Xp0K1nGjaY8p6PHQB+J9ZJuIvNp7ux3Noupq0hMd/dqEHXkfdmmGFOKTbGKWmw==", + "node_modules/stream-buffers": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", + "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==", "engines": { - "node": ">=12", - "npm": ">=6" + "node": ">= 0.10.0" } }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" + }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", "license": "MIT", "dependencies": { - "safe-buffer": "5.2.1" + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/conventional-changelog-conventionalcommits": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-5.0.0.tgz", - "integrity": "sha512-lCDbA+ZqVFQGUj7h9QBKoIpLhl8iihkO0nCTyRNzuXtcd7ubODpYB04IFy31JloiJgG0Uovu8ot8oxRzn7Nwtw==", + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": { - "compare-func": "^2.0.0", - "lodash": "^4.17.15", - "q": "^1.5.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, + "node_modules/strnum": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz", + "integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT" }, - "node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", - "license": "MIT", + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">=18" + "node": ">=8" } }, - "node_modules/cookie-es": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-2.0.0.tgz", - "integrity": "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==" - }, - "node_modules/core-js-compat": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz", - "integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4" + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, + "node_modules/swap-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/swap-case/-/swap-case-3.0.3.tgz", + "integrity": "sha512-6p4op8wE9CQv7uDFzulI6YXUw4lD9n4oQierdbFThEKVWVQcbQcUjdP27W8XE7V4QnWmnq9jueSHceyyQnqQVA==", + "license": "MIT" + }, + "node_modules/systeminformation": { + "version": "5.31.2", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.2.tgz", + "integrity": "sha512-ietGQGFhhZNBPgNv9vljgT8gzbYgQr6t0yGAo0Vdb5Jyilb574Vp+AuX2Or9rpBq3ho4mJRawLIUa9+CiILJdg==", + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], "bin": { - "create-jest": "bin/create-jest.js" + "systeminformation": "lib/cli.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/cron-parser": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", - "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "license": "BSD-3-Clause", "dependencies": { - "luxon": "^3.2.1" + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=10.0.0" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/tar-fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "pump": "^3.0.0", + "tar-stream": "^3.1.5" }, - "engines": { - "node": ">= 8" + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" } }, - "node_modules/crypto-js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } }, - "node_modules/custom-error-instance": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz", - "integrity": "sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg==", - "dev": true - }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "dev": true, + "node_modules/tarn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", + "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==", "engines": { - "node": "*" + "node": ">=8.0.0" } }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/tdigest": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", + "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "bintrees": "1.0.2" } }, - "node_modules/dedent": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", - "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, + "node_modules/thread-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", + "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=20" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "engines": { - "node": ">=0.4.0" + "node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" } }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "node_modules/tildify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", + "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==", "engines": { - "node": ">=0.10" + "node": ">=8" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=18" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, "engines": { - "node": ">=6" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.3.1" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, + "node_modules/title-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", + "integrity": "sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==", + "license": "MIT", "dependencies": { - "esutils": "^2.0.2" - }, + "tslib": "^2.0.3" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, "engines": { - "node": ">=6.0.0" + "node": ">=14.14" } }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "dependencies": { - "is-obj": "^2.0.0" + "is-number": "^7.0.0" }, "engines": { - "node": ">=8" + "node": ">=8.0" } }, - "node_modules/dotenv": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz", - "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==", + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", "engines": { "node": ">=12" } }, - "node_modules/dunder-proto": { + "node_modules/toidentifier": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, "engines": { - "node": ">= 0.4" + "node": ">=0.6" } }, - "node_modules/duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "node_modules/dynamic-dedupe": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", - "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", - "dev": true, - "dependencies": { - "xtend": "^4.0.0" - } + "node_modules/ts-algebra": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-1.2.2.tgz", + "integrity": "sha512-kloPhf1hq3JbCPOTYoOWDKxebWjNb2o/LKnNfkWhxVVisFFmMJPPdJeGoGmM+iRLyoXAR61e08Pb+vUXINg8aA==", + "dev": true }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "node_modules/tsx": { + "version": "4.19.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.4.tgz", + "integrity": "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "jake": "^10.8.5" + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" }, "bin": { - "ejs": "bin/cli.js" + "tsx": "dist/cli.mjs" }, "engines": { - "node": ">=0.10.0" + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.140", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.140.tgz", - "integrity": "sha512-o82Rj+ONp4Ip7Cl1r7lrqx/pXhbp/lh9DpKcMNscFJdh8ebyRofnc7Sh01B4jx403RI0oqTBvlZ7OBIZLMr2+Q==", + "node_modules/tus-js-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tus-js-client/-/tus-js-client-3.1.0.tgz", + "integrity": "sha512-Hfpc8ho4C9Lhs/OflPUA/nHUHZJUrKD5upoPBq7dYJJ9DQhWocsjJU2RZYfN16Y5n19j9dFDszwCvVZ5sfcogw==", "dev": true, - "license": "ISC" + "dependencies": { + "buffer-from": "^1.1.2", + "combine-errors": "^3.0.3", + "is-stream": "^2.0.0", + "js-base64": "^3.7.2", + "lodash.throttle": "^4.1.1", + "proper-lockfile": "^4.1.2", + "url-parse": "^1.5.7" + } }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "node_modules/typed-emitter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-0.1.0.tgz", + "integrity": "sha512-Tfay0l6gJMP5rkil8CzGbLthukn+9BN/VXWcABVFPjOoelJ+koW8BuPZYk+h/L+lEeIp1fSzVRiWRPIjKVjPdg==" }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, + "node_modules/undici": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", + "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-thread-interceptor": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/undici-thread-interceptor/-/undici-thread-interceptor-1.3.1.tgz", + "integrity": "sha512-s/TUkGeVj+C4Rr3Vsy84Kewj97m2QBNx6IjMGM1nzfoKWqNlcLqtFQCbXQjobRJQKi3u9Hc796/YvJIv7W7qWQ==", "license": "MIT", "dependencies": { - "is-arrayish": "^0.2.1" + "fastq": "^1.19.1", + "hyperid": "^3.2.0", + "light-my-request": "^6.5.1", + "undici": "^7.0.0" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "license": "MIT", "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" + "node": ">=18" }, - "engines": { - "node": ">= 0.4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, "engines": { - "node": ">= 0.4" + "node": ">= 10.0.0" } }, - "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "license": "MIT", - "engines": { - "node": ">=6" + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "node_modules/uuid-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/uuid-parse/-/uuid-parse-1.1.0.tgz", + "integrity": "sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==", "license": "MIT" }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "node_modules/vitest": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", + "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.4", + "@vitest/mocker": "4.1.4", + "@vitest/pretty-format": "4.1.4", + "@vitest/runner": "4.1.4", + "@vitest/snapshot": "4.1.4", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" }, "bin": { - "eslint": "bin/eslint.js" + "vitest": "vitest.mjs" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", - "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "eslint": ">=7.0.0" + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.4", + "@vitest/browser-preview": "4.1.4", + "@vitest/browser-webdriverio": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", + "@vitest/coverage-v8": "4.1.4", + "@vitest/ui": "4.1.4", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } } }, - "node_modules/eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", + "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", "dev": true, + "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0" + "@vitest/spy": "4.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" }, - "engines": { - "node": ">=12.0.0" + "funding": { + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { - "eslint-config-prettier": { + "msw": { + "optional": true + }, + "vite": { "optional": true } } }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=12" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/vitest/node_modules/vite": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", + "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.15", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "node_modules/wattpm": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/wattpm/-/wattpm-3.52.0.tgz", + "integrity": "sha512-Lm8wkRLxjWkfKEXaAt2B6R5jeZ1GIt4UKCDL9sx+zS+lVd5r600kOvZ93sTFGzGetaD/ENXJtY35r6/YccEWIw==", + "license": "Apache-2.0", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@fastify/websocket": "^11.0.0", + "@platformatic/control": "3.52.0", + "@platformatic/foundation": "3.52.0", + "@platformatic/runtime": "3.52.0", + "colorette": "^2.0.20", + "pino-pretty": "^13.0.0", + "split2": "^4.2.0", + "table": "^6.8.2" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "bin": { + "watt": "bin/cli.js", + "wattpm": "bin/cli.js" + }, + "engines": { + "node": ">=22.19.0" } }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, + "node_modules/wattpm/node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 8" } }, - "node_modules/eslint/node_modules/find-up": { + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "isexe": "^2.0.0" }, - "engines": { - "node": ">=10" + "bin": { + "node-which": "bin/node-which" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 8" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, + "license": "MIT", "dependencies": { - "is-glob": "^4.0.3" + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" }, "engines": { - "node": ">=10.13.0" + "node": ">=8" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dependencies": { - "p-locate": "^5.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dependencies": { - "p-limit": "^3.0.2" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/esm": { - "version": "3.2.25", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", - "engines": { - "node": ">=6" - } + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "node": ">=10.0.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" }, - "engines": { - "node": ">=0.10" + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", "dependencies": { - "estraverse": "^5.2.0" + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" }, "engines": { - "node": ">=4.0" + "node": ">=4.0.0" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", "engines": { "node": ">=4.0" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "engines": { - "node": ">=0.10.0" + "node": ">=0.4" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "bin": { + "yaml": "bin.mjs" }, "engines": { - "node": ">=10" + "node": ">= 14.6" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" + "url": "https://github.com/sponsors/eemeli" } }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "license": "MIT", + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/fast-copy": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-2.1.3.tgz", - "integrity": "sha512-LDzYKNTHhD+XOp8wGMuCkY4eTxFZOOycmpwLBiuF3r3OjOmZnURRD8t2dUAbmKuXGbo/MGggwbSjcBdp8QT0+g==", - "dev": true - }, - "node_modules/fast-decode-uri-component": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", - "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "engines": { - "node": ">=8.6.0" + "node": ">=12" } }, - "node_modules/fast-json-parse": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fast-json-parse/-/fast-json-parse-1.0.3.tgz", - "integrity": "sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw==" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-json-stringify": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.0.1.tgz", - "integrity": "sha512-s7SJE83QKBZwg54dIbD5rCtzOBVD43V1ReWXXYqBgwCwHLYAAT0RQc/FmrQglXqWPpz6omtryJQOau5jI4Nrvg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", "license": "MIT", - "dependencies": { - "@fastify/merge-json-schemas": "^0.2.0", - "ajv": "^8.12.0", - "ajv-formats": "^3.0.1", - "fast-uri": "^3.0.0", - "json-schema-ref-resolver": "^2.0.0", - "rfdc": "^1.2.0" + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fast-querystring": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", - "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", - "license": "MIT", - "dependencies": { - "fast-decode-uri-component": "^1.0.1" + } + }, + "dependencies": { + "@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" } }, - "node_modules/fast-redact": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.1.tgz", - "integrity": "sha512-odVmjC8x8jNeMZ3C+rPMESzXVSEU8tSWSHv9HFxP2mm89G/1WwqhrerJDQm9Zus8X6aoRgQDThKqptdNA6bt+A==", - "engines": { - "node": ">=6" + "@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" } }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fast-xml-parser": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", - "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - ], - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/fastify": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.6.0.tgz", - "integrity": "sha512-9j2r9TnwNsfGiCKGYT0Voqy244qwcoYM9qvNi/i+F8sNNWDnqUEVuGYNc9GyjldhXmMlJmVPS6gI1LdvjYGRJw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/ajv-compiler": "^4.0.0", - "@fastify/error": "^4.0.0", - "@fastify/fast-json-stringify-compiler": "^5.0.0", - "@fastify/proxy-addr": "^5.0.0", - "abstract-logging": "^2.0.1", - "avvio": "^9.0.0", - "fast-json-stringify": "^6.0.0", - "find-my-way": "^9.0.0", - "light-my-request": "^6.0.0", - "pino": "^9.0.0", - "process-warning": "^5.0.0", - "rfdc": "^1.3.1", - "secure-json-parse": "^4.0.0", - "semver": "^7.6.0", - "toad-cache": "^3.7.0" - } - }, - "node_modules/fastify-metrics": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/fastify-metrics/-/fastify-metrics-12.1.0.tgz", - "integrity": "sha512-EpbT+W1jm8kMbkCPvfW4j23y3BZlXGOcO6+75EFTKDxbJIyXbldrFIoVoP0oD4CsqrKeIARvrOjbZNqK5MdRwQ==", - "license": "MIT", - "dependencies": { - "fastify-plugin": "^5.0.0", - "prom-client": "^15.1.3" - }, - "peerDependencies": { - "fastify": ">=5" - } - }, - "node_modules/fastify-plugin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.0.1.tgz", - "integrity": "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==", - "license": "MIT" - }, - "node_modules/fastify-xml-body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/fastify-xml-body-parser/-/fastify-xml-body-parser-2.2.0.tgz", - "integrity": "sha512-Jxltec0Iin4QX+DEQoYCyGmU5cTRtI0x22mRT/3FBQMhTEn7KNTHnnEtbyN3+6SLgW8cSirnOe1t8vqn77vR+Q==", - "license": "MIT", - "dependencies": { - "fast-xml-parser": "^4.1.2", - "fastify-plugin": "^3.0.0" - } - }, - "node_modules/fastify-xml-body-parser/node_modules/fastify-plugin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.1.tgz", - "integrity": "sha512-qKcDXmuZadJqdTm6vlCqioEbyewF60b/0LOFCcYN1B6BIZGlYJumWWOYs70SFYLDAH4YqdE1cxH/RKMG7rFxgA==" - }, - "node_modules/fastify/node_modules/secure-json-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.0.0.tgz", - "integrity": "sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/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/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-my-way": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.3.0.tgz", - "integrity": "sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-querystring": "^1.0.0", - "safe-regex2": "^5.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/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==", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fs-xattr": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/fs-xattr/-/fs-xattr-0.3.1.tgz", - "integrity": "sha512-UVqkrEW0GfDabw4C3HOrFlxKfx0eeigfRne69FxSBdHIP8Qt5Sq6Pu3RM9KmMlkygtC4pPKkj5CiPO5USnj2GA==", - "hasInstallScript": true, - "os": [ - "!win32" - ], - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gaxios": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", - "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/gcp-metadata": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", - "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", - "dependencies": { - "gaxios": "^6.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/generic-pool": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", - "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", - "optional": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-tsconfig": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", - "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", - "dev": true, - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/getopts": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", - "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==" - }, - "node_modules/glob": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", - "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob/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==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-in-the-middle": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.11.0.tgz", - "integrity": "sha512-5DimNQGoe0pLUHbR9qK84iWaWjjbsxiqXnw6Qz64+azRgleqv9k2kTt5fw7QsOpmaGYtuxxursnPPsnTKEx10Q==", - "dependencies": { - "acorn": "^8.8.2", - "acorn-import-attributes": "^1.9.5", - "cjs-module-lexer": "^1.2.2", - "module-details-from-path": "^1.0.3" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/ioredis": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz", - "integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==", - "dependencies": { - "@ioredis/commands": "^1.1.1", - "cluster-key-slot": "^1.1.0", - "debug": "^4.3.4", - "denque": "^2.1.0", - "lodash.defaults": "^4.2.0", - "lodash.isarguments": "^3.1.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0", - "standard-as-callback": "^2.1.0" - }, - "engines": { - "node": ">=12.22.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ioredis" - } - }, - "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-retry-allowed": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", - "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/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, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", - "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jose": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz", - "integrity": "sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/js-base64": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.5.tgz", - "integrity": "sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==", - "dev": true - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-ref-resolver": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-2.0.1.tgz", - "integrity": "sha512-HG0SIB9X4J8bwbxCbnd5FfPEbcXAJYTi1pBJeP/QPON+w8ovSME8iRG+ElHNxZNX2Qh6eYn1GdzJFS4cDFfx0Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/json-schema-resolver": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-3.0.0.tgz", - "integrity": "sha512-HqMnbz0tz2DaEJ3ntsqtx3ezzZyDE7G56A/pPY/NGmrPu76UzsWquOpHFRAf5beTNXoH2LU5cQePVvRli1nchA==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "fast-uri": "^3.0.5", - "rfdc": "^1.1.4" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/Eomm/json-schema-resolver?sponsor=1" - } - }, - "node_modules/json-schema-to-ts": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.0.0.tgz", - "integrity": "sha512-2adDesYifYEXYxNySx3gG0RR69rDWIjqAFzK/JPXdOvjHLZ/UP6d2rkpy6a+AxyhtRp2SvFPZ4+EW36jBinUbA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.18.3", - "@types/json-schema": "^7.0.9", - "ts-algebra": "^1.2.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/knex": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/knex/-/knex-3.1.0.tgz", - "integrity": "sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==", - "dependencies": { - "colorette": "2.0.19", - "commander": "^10.0.0", - "debug": "4.3.4", - "escalade": "^3.1.1", - "esm": "^3.2.25", - "get-package-type": "^0.1.0", - "getopts": "2.3.0", - "interpret": "^2.2.0", - "lodash": "^4.17.21", - "pg-connection-string": "2.6.2", - "rechoir": "^0.8.0", - "resolve-from": "^5.0.0", - "tarn": "^3.0.2", - "tildify": "2.0.0" - }, - "bin": { - "knex": "bin/cli.js" - }, - "engines": { - "node": ">=16" - }, - "peerDependenciesMeta": { - "better-sqlite3": { - "optional": true - }, - "mysql": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-native": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "tedious": { - "optional": true - } - } - }, - "node_modules/knex/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/light-my-request": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", - "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause", - "dependencies": { - "cookie": "^1.0.1", - "process-warning": "^4.0.0", - "set-cookie-parser": "^2.6.0" - } - }, - "node_modules/light-my-request/node_modules/process-warning": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", - "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash._baseiteratee": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz", - "integrity": "sha512-nqB9M+wITz0BX/Q2xg6fQ8mLkyfF7MU7eE+MNBNjTHFKeKaZAPEzEg+E8LWxKWf1DQVflNEn9N49yAuqKh2mWQ==", - "dev": true, - "dependencies": { - "lodash._stringtopath": "~4.8.0" - } - }, - "node_modules/lodash._basetostring": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz", - "integrity": "sha512-SwcRIbyxnN6CFEEK4K1y+zuApvWdpQdBHM/swxP962s8HIxPO3alBH5t3m/dl+f4CMUug6sJb7Pww8d13/9WSw==", - "dev": true - }, - "node_modules/lodash._baseuniq": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", - "integrity": "sha512-Ja1YevpHZctlI5beLA7oc5KNDhGcPixFhcqSiORHNsp/1QTv7amAXzw+gu4YOvErqVlMVyIJGgtzeepCnnur0A==", - "dev": true, - "dependencies": { - "lodash._createset": "~4.0.0", - "lodash._root": "~3.0.0" - } - }, - "node_modules/lodash._createset": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", - "integrity": "sha512-GTkC6YMprrJZCYU3zcqZj+jkXkrXzq3IPBcF/fIPpNEAB4hZEtXU8zp/RwKOvZl43NUmwDbyRk3+ZTbeRdEBXA==", - "dev": true - }, - "node_modules/lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ==", - "dev": true - }, - "node_modules/lodash._stringtopath": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz", - "integrity": "sha512-SXL66C731p0xPDC5LZg4wI5H+dJo/EO4KTqOMwLYCH3+FmmfAKJEZCm6ohGpI+T1xwsDsJCfL4OnhorllvlTPQ==", - "dev": true, - "dependencies": { - "lodash._basetostring": "~4.12.0" - } - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" - }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" - }, - "node_modules/lodash.uniqby": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz", - "integrity": "sha512-IRt7cfTtHy6f1aRVA5n7kT8rgN3N1nH6MOWLcHfpWG2SH19E3JksLK38MktLxZDhlAjCP9jpIXkOnRXlu6oByQ==", - "dev": true, - "dependencies": { - "lodash._baseiteratee": "~4.7.0", - "lodash._baseuniq": "~4.6.0" - } - }, - "node_modules/logflare-transport-core": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/logflare-transport-core/-/logflare-transport-core-0.4.1.tgz", - "integrity": "sha512-9q61GYsRSf4vZHXRvBClamFvPSRvfgWkDaBkYg4Q4DuofcpwikxPROB/h8rVX3xyE8jrKqKM0mAlrqCzJQS3MQ==", - "license": "MIT" - }, - "node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" - }, - "node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/luxon": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", - "integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/md5-file": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-5.0.0.tgz", - "integrity": "sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw==", - "bin": { - "md5-file": "cli.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/module-details-from-path": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", - "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/multistream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz", - "integrity": "sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "once": "^1.4.0", - "readable-stream": "^3.6.0" - } - }, - "node_modules/mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "dev": true, - "bin": { - "mustache": "bin/mustache" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/object-sizeof": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/object-sizeof/-/object-sizeof-2.6.4.tgz", - "integrity": "sha512-YuJAf7Bi61KROcYmXm8RCeBrBw8UOaJDzTm1gp0eU7RjYi1xEte3/Nmg/VyPaHcJZ3sNojs1Y0xvSrgwkLmcFw==", - "dependencies": { - "buffer": "^6.0.3" - } - }, - "node_modules/object-sizeof/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/on-exit-leak-free": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", - "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==" - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/openapi-types": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", - "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "license": "MIT" - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", - "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/pg": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.0.tgz", - "integrity": "sha512-7SKfdvP8CTNXjMUzfcVTaI+TDzBEeaUnVwiVGZQD1Hh33Kpev7liQba9uLd4CfN8r9mCVsD0JIpq03+Unpz+kg==", - "dependencies": { - "pg-connection-string": "^2.9.0", - "pg-pool": "^3.10.0", - "pg-protocol": "^1.10.0", - "pg-types": "2.2.0", - "pgpass": "1.0.5" - }, - "engines": { - "node": ">= 8.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.2.5" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-boss": { - "version": "10.3.2", - "resolved": "git+ssh://git@github.com/supabase/pg-boss.git#ca012f295507400c1a9ada9dbf0f4d11cdbe3a2f", - "license": "MIT", - "dependencies": { - "cron-parser": "^4.9.0", - "pg": "^8.16.0", - "serialize-error": "^8.1.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/pg-cloudflare": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.5.tgz", - "integrity": "sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg==", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", - "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==" - }, - "node_modules/pg-format": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pg-format/-/pg-format-1.0.4.tgz", - "integrity": "sha1-J3NCNsKtP05QZJFaWTNOIAQKgo4=", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-listen": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/pg-listen/-/pg-listen-1.7.0.tgz", - "integrity": "sha512-MKDwKLm4ryhy7iq1yw1K1MvUzBdTkaT16HZToddX9QaT8XSdt3Kins5mYH6DLECGFzFWG09VdXvWOIYogjXrsg==", - "dependencies": { - "debug": "^4.1.1", - "pg-format": "^1.0.4", - "typed-emitter": "^0.1.0" - }, - "peerDependencies": { - "pg": "7.x || 8.x" - } - }, - "node_modules/pg-pool": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.0.tgz", - "integrity": "sha512-DzZ26On4sQ0KmqnO34muPcmKbhrjmyiO4lCCR0VwEd7MjmiKf5NTg/6+apUEu0NF7ESa37CGzFxH513CoUmWnA==", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.0.tgz", - "integrity": "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q==" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pg/node_modules/pg-connection-string": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.0.tgz", - "integrity": "sha512-P2DEBKuvh5RClafLngkAuGe9OUlFV7ebu8w1kmaaOgPcpJd1RIFh7otETfI6hAR8YupOLFTY7nuvvIn7PLciUQ==" - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "dependencies": { - "split2": "^4.1.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pino": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.7.0.tgz", - "integrity": "sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^5.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^3.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-abstract-transport": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz", - "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==", - "dependencies": { - "readable-stream": "^4.0.0", - "split2": "^4.0.0" - } - }, - "node_modules/pino-abstract-transport/node_modules/readable-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.1.0.tgz", - "integrity": "sha512-sVisi3+P2lJ2t0BPbpK629j8wRW06yKGJUcaLAGXPAUhyUxVJm7VsCTit1PFgT4JHUDMrGNR+ZjSKpzGaRF3zw==", - "dependencies": { - "abort-controller": "^3.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/pino-logflare": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/pino-logflare/-/pino-logflare-0.5.2.tgz", - "integrity": "sha512-iRq4GLQPHb5FsTa8ZQOP+Z1w7jSl8cxkJRX5Q3oYlPLQmBVEclHB0wQ9qT2hHExlycBWZ2Pz8KnijPstpBY9jg==", - "license": "MIT", - "dependencies": { - "batch2": "^1.0.6", - "commander": "^5.0.0", - "fast-json-parse": "^1.0.3", - "logflare-transport-core": "^0.4.1", - "pino-abstract-transport": "^1.0.0", - "pumpify": "^2.0.1", - "split2": "^3.1.1", - "through2": "^3.0.1" - }, - "bin": { - "pino-logflare": "dist/cli.js" - }, - "peerDependencies": { - "pino": "*" - } - }, - "node_modules/pino-logflare/node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pino-logflare/node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "node_modules/pino-pretty": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-8.1.0.tgz", - "integrity": "sha512-oKfI8qKXR2a3haHs/X8iB6QSnWLqoOGAjwxIAXem4+XOGIGNw7IKpozId1uE7j89Rj46HIfWnGbAgmQmr8+yRw==", - "dev": true, - "dependencies": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^2.1.1", - "fast-safe-stringify": "^2.1.1", - "help-me": "^4.0.1", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^1.0.0", - "pino-abstract-transport": "^1.0.0", - "pump": "^3.0.0", - "readable-stream": "^4.0.0", - "secure-json-parse": "^2.4.0", - "sonic-boom": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "bin": { - "pino-pretty": "bin.js" - } - }, - "node_modules/pino-pretty/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/pino-pretty/node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/pino-pretty/node_modules/help-me": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.0.1.tgz", - "integrity": "sha512-PLv01Z+OhEPKj2QPYB4kjoCUkopYNPUK3EROlaPIf5bib752fZ+VCvGDAoA+FXo/OwCyLEA4D2e0mX8+Zhcplw==", - "dev": true, - "dependencies": { - "glob": "^8.0.0", - "readable-stream": "^3.6.0" - } - }, - "node_modules/pino-pretty/node_modules/help-me/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pino-pretty/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/pino-pretty/node_modules/on-exit-leak-free": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-1.0.0.tgz", - "integrity": "sha512-Ve8ubhrXRdnuCJ5bQSQpP3uaV43K1PMcOfSRC1pqHgRZommXCgsXwh08jVC5NpjwScE23BPDwDvVg4cov3mwjw==", - "dev": true - }, - "node_modules/pino-pretty/node_modules/readable-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.1.0.tgz", - "integrity": "sha512-sVisi3+P2lJ2t0BPbpK629j8wRW06yKGJUcaLAGXPAUhyUxVJm7VsCTit1PFgT4JHUDMrGNR+ZjSKpzGaRF3zw==", - "dev": true, - "dependencies": { - "abort-controller": "^3.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/pino-std-serializers": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", - "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", - "license": "MIT" - }, - "node_modules/pino/node_modules/pino-abstract-transport": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", - "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", - "license": "MIT", - "dependencies": { - "split2": "^4.0.0" - } - }, - "node_modules/pino/node_modules/sonic-boom": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", - "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-migrations": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/postgres-migrations/-/postgres-migrations-5.3.0.tgz", - "integrity": "sha512-gnTHWJZVWbW8T3mXIxJm1JRU853TqBVWkhgfsTJr7zqT3VexjRmJj9kNi96rVhfTezDU4FVW6pf701kLOZiKIA==", - "dependencies": { - "pg": "^8.6.0", - "sql-template-strings": "^2.2.2" - }, - "bin": { - "pg-validate-migrations": "dist/bin/validate.js" - }, - "engines": { - "node": ">10.17.0" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/process-warning": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/prom-client": { - "version": "15.1.3", - "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", - "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.4.0", - "tdigest": "^0.1.1" - }, - "engines": { - "node": "^16 || ^18 || >=20" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proper-lockfile/node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/protobufjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "dependencies": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" - }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", - "engines": { - "node": ">=4" - } - }, - "node_modules/redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", - "dependencies": { - "redis-errors": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" - }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", - "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~3.0.2" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-in-the-middle": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.4.0.tgz", - "integrity": "sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ==", - "dependencies": { - "debug": "^4.3.5", - "module-details-from-path": "^1.0.3", - "resolve": "^1.22.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/require-in-the-middle/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/require-in-the-middle/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/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/resolve-tspaths": { - "version": "0.8.19", - "resolved": "https://registry.npmjs.org/resolve-tspaths/-/resolve-tspaths-0.8.19.tgz", - "integrity": "sha512-yZkXNYyHdVytOkJLhbib7TFpaMVElk9auO9R1jDmOSXPUWEjy+V44VqX77RIQ+kf0UJIlAGRDK/yrbfwlu1UWg==", - "dev": true, - "dependencies": { - "ansi-colors": "4.1.3", - "commander": "12.0.0", - "fast-glob": "3.3.2" - }, - "bin": { - "resolve-tspaths": "dist/main.js" - }, - "peerDependencies": { - "typescript": ">=3.0.3" - } - }, - "node_modules/resolve-tspaths/node_modules/commander": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", - "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ret": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", - "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-regex2": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", - "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "ret": "~0.5.0" - } - }, - "node_modules/safe-stable-stringify": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", - "integrity": "sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/sax": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" - }, - "node_modules/secure-json-parse": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", - "dev": true - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-error": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", - "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/shimmer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/sonic-boom": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.2.0.tgz", - "integrity": "sha512-SbbZ+Kqj/XIunvIAgUZRlqd6CGQYq71tRRbXR92Za8J/R3Yh4Av+TWENiSiEgnlwckYLyP0YZQWVfyNC0dzLaA==", - "dev": true, - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/split2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", - "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/sql-template-strings": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/sql-template-strings/-/sql-template-strings-2.2.2.tgz", - "integrity": "sha1-PxFQiiWt384hejBCqdMAwxk7lv8=", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/srvx": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.2.8.tgz", - "integrity": "sha512-9HG8eqhHLsvzhW4nJTg/4Sob1Sw/fB6S8Yzvf+iUD6l5CT4D0CXfPcFfh1r2eJFNHC0JOezK+YrgoufA5goYMQ==", - "dependencies": { - "cookie-es": "^2.0.0" - }, - "engines": { - "node": ">=20.11.1" - } - }, - "node_modules/stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/standard-as-callback": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", - "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "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==", - "dependencies": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, - "node_modules/stream-buffers": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", - "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tarn": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", - "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/tdigest": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", - "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", - "dependencies": { - "bintrees": "1.0.2" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/thread-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", - "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", - "license": "MIT", - "dependencies": { - "real-require": "^0.2.0" - } - }, - "node_modules/through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - }, - "node_modules/tildify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", - "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toad-cache": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", - "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/ts-algebra": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-1.2.2.tgz", - "integrity": "sha512-kloPhf1hq3JbCPOTYoOWDKxebWjNb2o/LKnNfkWhxVVisFFmMJPPdJeGoGmM+iRLyoXAR61e08Pb+vUXINg8aA==", - "dev": true - }, - "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", - "dev": true, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/ts-jest": { - "version": "29.3.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.2.tgz", - "integrity": "sha512-bJJkrWc6PjFVz5g2DGCNUo8z7oFEYaz1xP1NpeDU7KNLMWPpEyV8Chbpkn8xjzgRDpQhnGMyvyldoL7h8JXyug==", - "dev": true, - "license": "MIT", - "dependencies": { - "bs-logger": "^0.2.6", - "ejs": "^3.1.10", - "fast-json-stable-stringify": "^2.1.0", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.1", - "type-fest": "^4.39.1", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz", - "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ts-node-dev": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.1.8.tgz", - "integrity": "sha512-Q/m3vEwzYwLZKmV6/0VlFxcZzVV/xcgOt+Tx/VjaaRHyiBcFlV0541yrT09QjzzCxlDZ34OzKjrFAynlmtflEg==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.1", - "dynamic-dedupe": "^0.3.0", - "minimist": "^1.2.5", - "mkdirp": "^1.0.4", - "resolve": "^1.0.0", - "rimraf": "^2.6.1", - "source-map-support": "^0.5.12", - "tree-kill": "^1.2.2", - "ts-node": "^9.0.0", - "tsconfig": "^7.0.0" - }, - "bin": { - "ts-node-dev": "lib/bin.js", - "tsnd": "lib/bin.js" - }, - "engines": { - "node": ">=0.8.0" - }, - "peerDependencies": { - "node-notifier": "*", - "typescript": "*" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/ts-node-dev/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ts-node-dev/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/ts-node-dev/node_modules/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "dependencies": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "typescript": ">=2.7" - } - }, - "node_modules/tsconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", - "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", - "dev": true, - "dependencies": { - "@types/strip-bom": "^3.0.0", - "@types/strip-json-comments": "0.0.30", - "strip-bom": "^3.0.0", - "strip-json-comments": "^2.0.0" - } - }, - "node_modules/tsconfig/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/tsconfig/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" - }, - "node_modules/tsx": { - "version": "4.19.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.4.tgz", - "integrity": "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.25.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/tus-js-client": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tus-js-client/-/tus-js-client-3.1.0.tgz", - "integrity": "sha512-Hfpc8ho4C9Lhs/OflPUA/nHUHZJUrKD5upoPBq7dYJJ9DQhWocsjJU2RZYfN16Y5n19j9dFDszwCvVZ5sfcogw==", - "dev": true, - "dependencies": { - "buffer-from": "^1.1.2", - "combine-errors": "^3.0.3", - "is-stream": "^2.0.0", - "js-base64": "^3.7.2", - "lodash.throttle": "^4.1.1", - "proper-lockfile": "^4.1.2", - "url-parse": "^1.5.7" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-emitter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-0.1.0.tgz", - "integrity": "sha512-Tfay0l6gJMP5rkil8CzGbLthukn+9BN/VXWcABVFPjOoelJ+koW8BuPZYk+h/L+lEeIp1fSzVRiWRPIjKVjPdg==" - }, - "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "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" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/xml2js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", - "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true - }, - "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@aws-crypto/crc32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", - "requires": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - } - }, - "@aws-crypto/crc32c": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", - "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", - "requires": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - }, - "dependencies": { - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - } - } - } - }, - "@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==", - "requires": { - "@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" - }, - "dependencies": { - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - } - } - } - }, - "@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==", - "requires": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "requires": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - }, - "dependencies": { - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/client-ecs": { - "version": "3.795.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.795.0.tgz", - "integrity": "sha512-UQnd5Ft8jDBZW4JpkKceq49/2BDx3go/c8KNBQOaFK52DcS6k+6BSt0S5ZW9oP2w5MTvQJUnnbIa4PH9IRfXEA==", - "requires": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.775.0", - "@aws-sdk/credential-provider-node": "3.787.0", - "@aws-sdk/middleware-host-header": "3.775.0", - "@aws-sdk/middleware-logger": "3.775.0", - "@aws-sdk/middleware-recursion-detection": "3.775.0", - "@aws-sdk/middleware-user-agent": "3.787.0", - "@aws-sdk/region-config-resolver": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@aws-sdk/util-endpoints": "3.787.0", - "@aws-sdk/util-user-agent-browser": "3.775.0", - "@aws-sdk/util-user-agent-node": "3.787.0", - "@smithy/config-resolver": "^4.1.0", - "@smithy/core": "^3.2.0", - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/hash-node": "^4.0.2", - "@smithy/invalid-dependency": "^4.0.2", - "@smithy/middleware-content-length": "^4.0.2", - "@smithy/middleware-endpoint": "^4.1.0", - "@smithy/middleware-retry": "^4.1.0", - "@smithy/middleware-serde": "^4.0.3", - "@smithy/middleware-stack": "^4.0.2", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/node-http-handler": "^4.0.4", - "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "@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.8", - "@smithy/util-defaults-mode-node": "^4.0.8", - "@smithy/util-endpoints": "^3.0.2", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-retry": "^4.0.2", - "@smithy/util-utf8": "^4.0.0", - "@smithy/util-waiter": "^4.0.3", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "dependencies": { - "@aws-sdk/client-sso": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.787.0.tgz", - "integrity": "sha512-L8R+Mh258G0DC73ktpSVrG4TT9i2vmDLecARTDR/4q5sRivdDQSL5bUp3LKcK80Bx+FRw3UETIlX6mYMLL9PJQ==", - "requires": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.775.0", - "@aws-sdk/middleware-host-header": "3.775.0", - "@aws-sdk/middleware-logger": "3.775.0", - "@aws-sdk/middleware-recursion-detection": "3.775.0", - "@aws-sdk/middleware-user-agent": "3.787.0", - "@aws-sdk/region-config-resolver": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@aws-sdk/util-endpoints": "3.787.0", - "@aws-sdk/util-user-agent-browser": "3.775.0", - "@aws-sdk/util-user-agent-node": "3.787.0", - "@smithy/config-resolver": "^4.1.0", - "@smithy/core": "^3.2.0", - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/hash-node": "^4.0.2", - "@smithy/invalid-dependency": "^4.0.2", - "@smithy/middleware-content-length": "^4.0.2", - "@smithy/middleware-endpoint": "^4.1.0", - "@smithy/middleware-retry": "^4.1.0", - "@smithy/middleware-serde": "^4.0.3", - "@smithy/middleware-stack": "^4.0.2", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/node-http-handler": "^4.0.4", - "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "@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.8", - "@smithy/util-defaults-mode-node": "^4.0.8", - "@smithy/util-endpoints": "^3.0.2", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-retry": "^4.0.2", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/core": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.775.0.tgz", - "integrity": "sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA==", - "requires": { - "@aws-sdk/types": "3.775.0", - "@smithy/core": "^3.2.0", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/signature-v4": "^5.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/util-middleware": "^4.0.2", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-env": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.775.0.tgz", - "integrity": "sha512-6ESVxwCbGm7WZ17kY1fjmxQud43vzJFoLd4bmlR+idQSWdqlzGDYdcfzpjDKTcivdtNrVYmFvcH1JBUwCRAZhw==", - "requires": { - "@aws-sdk/core": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@smithy/property-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-http": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.775.0.tgz", - "integrity": "sha512-PjDQeDH/J1S0yWV32wCj2k5liRo0ssXMseCBEkCsD3SqsU8o5cU82b0hMX4sAib/RkglCSZqGO0xMiN0/7ndww==", - "requires": { - "@aws-sdk/core": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/node-http-handler": "^4.0.4", - "@smithy/property-provider": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/util-stream": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-ini": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.787.0.tgz", - "integrity": "sha512-hc2taRoDlXn2uuNuHWDJljVWYrp3r9JF1a/8XmOAZhVUNY+ImeeStylHXhXXKEA4JOjW+5PdJj0f1UDkVCHJiQ==", - "requires": { - "@aws-sdk/core": "3.775.0", - "@aws-sdk/credential-provider-env": "3.775.0", - "@aws-sdk/credential-provider-http": "3.775.0", - "@aws-sdk/credential-provider-process": "3.775.0", - "@aws-sdk/credential-provider-sso": "3.787.0", - "@aws-sdk/credential-provider-web-identity": "3.787.0", - "@aws-sdk/nested-clients": "3.787.0", - "@aws-sdk/types": "3.775.0", - "@smithy/credential-provider-imds": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-node": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.787.0.tgz", - "integrity": "sha512-JioVi44B1vDMaK2CdzqimwvJD3uzvzbQhaEWXsGMBcMcNHajXAXf08EF50JG3ZhLrhhUsT1ObXpbTaPINOhh+g==", - "requires": { - "@aws-sdk/credential-provider-env": "3.775.0", - "@aws-sdk/credential-provider-http": "3.775.0", - "@aws-sdk/credential-provider-ini": "3.787.0", - "@aws-sdk/credential-provider-process": "3.775.0", - "@aws-sdk/credential-provider-sso": "3.787.0", - "@aws-sdk/credential-provider-web-identity": "3.787.0", - "@aws-sdk/types": "3.775.0", - "@smithy/credential-provider-imds": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-process": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.775.0.tgz", - "integrity": "sha512-A6k68H9rQp+2+7P7SGO90Csw6nrUEm0Qfjpn9Etc4EboZhhCLs9b66umUsTsSBHus4FDIe5JQxfCUyt1wgNogg==", - "requires": { - "@aws-sdk/core": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-sso": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.787.0.tgz", - "integrity": "sha512-fHc08bsvwm4+dEMEQKnQ7c1irEQmmxbgS+Fq41y09pPvPh31nAhoMcjBSTWAaPHvvsRbTYvmP4Mf12ZGr8/nfg==", - "requires": { - "@aws-sdk/client-sso": "3.787.0", - "@aws-sdk/core": "3.775.0", - "@aws-sdk/token-providers": "3.787.0", - "@aws-sdk/types": "3.775.0", - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-web-identity": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.787.0.tgz", - "integrity": "sha512-SobmCwNbk6TfEsF283mZPQEI5vV2j6eY5tOCj8Er4Lzraxu9fBPADV+Bib2A8F6jlB1lMPJzOuDCbEasSt/RIw==", - "requires": { - "@aws-sdk/core": "3.775.0", - "@aws-sdk/nested-clients": "3.787.0", - "@aws-sdk/types": "3.775.0", - "@smithy/property-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-host-header": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.775.0.tgz", - "integrity": "sha512-tkSegM0Z6WMXpLB8oPys/d+umYIocvO298mGvcMCncpRl77L9XkvSLJIFzaHes+o7djAgIduYw8wKIMStFss2w==", - "requires": { - "@aws-sdk/types": "3.775.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-logger": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.775.0.tgz", - "integrity": "sha512-FaxO1xom4MAoUJsldmR92nT1G6uZxTdNYOFYtdHfd6N2wcNaTuxgjIvqzg5y7QIH9kn58XX/dzf1iTjgqUStZw==", - "requires": { - "@aws-sdk/types": "3.775.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-recursion-detection": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.775.0.tgz", - "integrity": "sha512-GLCzC8D0A0YDG5u3F5U03Vb9j5tcOEFhr8oc6PDk0k0vm5VwtZOE6LvK7hcCSoAB4HXyOUM0sQuXrbaAh9OwXA==", - "requires": { - "@aws-sdk/types": "3.775.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-user-agent": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.787.0.tgz", - "integrity": "sha512-Lnfj8SmPLYtrDFthNIaNj66zZsBCam+E4XiUDr55DIHTGstH6qZ/q6vg0GfbukxwSmUcGMwSR4Qbn8rb8yd77g==", - "requires": { - "@aws-sdk/core": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@aws-sdk/util-endpoints": "3.787.0", - "@smithy/core": "^3.2.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/region-config-resolver": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.775.0.tgz", - "integrity": "sha512-40iH3LJjrQS3LKUJAl7Wj0bln7RFPEvUYKFxtP8a+oKFDO0F65F52xZxIJbPn6sHkxWDAnZlGgdjZXM3p2g5wQ==", - "requires": { - "@aws-sdk/types": "3.775.0", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/token-providers": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.787.0.tgz", - "integrity": "sha512-d7/NIqxq308Zg0RPMNrmn0QvzniL4Hx8Qdwzr6YZWLYAbUSvZYS2ppLR3BFWSkV6SsTJUx8BuDaj3P8vttkrog==", - "requires": { - "@aws-sdk/nested-clients": "3.787.0", - "@aws-sdk/types": "3.775.0", - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/types": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.775.0.tgz", - "integrity": "sha512-ZoGKwa4C9fC9Av6bdfqcW6Ix5ot05F/S4VxWR2nHuMv7hzfmAjTOcUiWT7UR4hM/U0whf84VhDtXN/DWAk52KA==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/util-endpoints": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.787.0.tgz", - "integrity": "sha512-fd3zkiOkwnbdbN0Xp9TsP5SWrmv0SpT70YEdbb8wAj2DWQwiCmFszaSs+YCvhoCdmlR3Wl9Spu0pGpSAGKeYvQ==", - "requires": { - "@aws-sdk/types": "3.775.0", - "@smithy/types": "^4.2.0", - "@smithy/util-endpoints": "^3.0.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/util-user-agent-browser": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.775.0.tgz", - "integrity": "sha512-txw2wkiJmZKVdDbscK7VBK+u+TJnRtlUjRTLei+elZg2ADhpQxfVAQl436FUeIv6AhB/oRHW6/K/EAGXUSWi0A==", - "requires": { - "@aws-sdk/types": "3.775.0", - "@smithy/types": "^4.2.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/util-user-agent-node": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.787.0.tgz", - "integrity": "sha512-mG7Lz8ydfG4SF9e8WSXiPQ/Lsn3n8A5B5jtPROidafi06I3ckV2WxyMLdwG14m919NoS6IOfWHyRGSqWIwbVKA==", - "requires": { - "@aws-sdk/middleware-user-agent": "3.787.0", - "@aws-sdk/types": "3.775.0", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/abort-controller": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.2.tgz", - "integrity": "sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/config-resolver": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.0.tgz", - "integrity": "sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A==", - "requires": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "tslib": "^2.6.2" - } - }, - "@smithy/core": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.2.0.tgz", - "integrity": "sha512-k17bgQhVZ7YmUvA8at4af1TDpl0NDMBuBKJl8Yg0nrefwmValU+CnA5l/AriVdQNthU/33H3nK71HrLgqOPr1Q==", - "requires": { - "@smithy/middleware-serde": "^4.0.3", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-stream": "^4.2.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@smithy/credential-provider-imds": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.2.tgz", - "integrity": "sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w==", - "requires": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "tslib": "^2.6.2" - } - }, - "@smithy/fetch-http-handler": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.2.tgz", - "integrity": "sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==", - "requires": { - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/util-base64": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@smithy/hash-node": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.2.tgz", - "integrity": "sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==", - "requires": { - "@smithy/types": "^4.2.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@smithy/invalid-dependency": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.2.tgz", - "integrity": "sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/middleware-content-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.2.tgz", - "integrity": "sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==", - "requires": { - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/middleware-endpoint": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.0.tgz", - "integrity": "sha512-xhLimgNCbCzsUppRTGXWkZywksuTThxaIB0HwbpsVLY5sceac4e1TZ/WKYqufQLaUy+gUSJGNdwD2jo3cXL0iA==", - "requires": { - "@smithy/core": "^3.2.0", - "@smithy/middleware-serde": "^4.0.3", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "@smithy/util-middleware": "^4.0.2", - "tslib": "^2.6.2" - } - }, - "@smithy/middleware-retry": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.0.tgz", - "integrity": "sha512-2zAagd1s6hAaI/ap6SXi5T3dDwBOczOMCSkkYzktqN1+tzbk1GAsHNAdo/1uzxz3Ky02jvZQwbi/vmDA6z4Oyg==", - "requires": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/service-error-classification": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-retry": "^4.0.2", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - } - }, - "@smithy/middleware-serde": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.3.tgz", - "integrity": "sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/middleware-stack": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.2.tgz", - "integrity": "sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/node-config-provider": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.2.tgz", - "integrity": "sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw==", - "requires": { - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/node-http-handler": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.4.tgz", - "integrity": "sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==", - "requires": { - "@smithy/abort-controller": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/property-provider": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.2.tgz", - "integrity": "sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/protocol-http": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.0.tgz", - "integrity": "sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/querystring-builder": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.2.tgz", - "integrity": "sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==", - "requires": { - "@smithy/types": "^4.2.0", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@smithy/querystring-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.2.tgz", - "integrity": "sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/service-error-classification": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.2.tgz", - "integrity": "sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ==", - "requires": { - "@smithy/types": "^4.2.0" - } - }, - "@smithy/shared-ini-file-loader": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.2.tgz", - "integrity": "sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/signature-v4": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.0.tgz", - "integrity": "sha512-4t5WX60sL3zGJF/CtZsUQTs3UrZEDO2P7pEaElrekbLqkWPYkgqNW1oeiNYC6xXifBnT9dVBOnNQRvOE9riU9w==", - "requires": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@smithy/smithy-client": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.2.0.tgz", - "integrity": "sha512-Qs65/w30pWV7LSFAez9DKy0Koaoh3iHhpcpCCJ4waj/iqwsuSzJna2+vYwq46yBaqO5ZbP9TjUsATUNxrKeBdw==", - "requires": { - "@smithy/core": "^3.2.0", - "@smithy/middleware-endpoint": "^4.1.0", - "@smithy/middleware-stack": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "@smithy/util-stream": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/url-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.2.tgz", - "integrity": "sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==", - "requires": { - "@smithy/querystring-parser": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-defaults-mode-browser": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.8.tgz", - "integrity": "sha512-ZTypzBra+lI/LfTYZeop9UjoJhhGRTg3pxrNpfSTQLd3AJ37r2z4AXTKpq1rFXiiUIJsYyFgNJdjWRGP/cbBaQ==", - "requires": { - "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-defaults-mode-node": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.8.tgz", - "integrity": "sha512-Rgk0Jc/UDfRTzVthye/k2dDsz5Xxs9LZaKCNPgJTRyoyBoeiNCnHsYGOyu1PKN+sDyPnJzMOz22JbwxzBp9NNA==", - "requires": { - "@smithy/config-resolver": "^4.1.0", - "@smithy/credential-provider-imds": "^4.0.2", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-endpoints": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.2.tgz", - "integrity": "sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ==", - "requires": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-middleware": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.2.tgz", - "integrity": "sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-retry": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.2.tgz", - "integrity": "sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg==", - "requires": { - "@smithy/service-error-classification": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.0.tgz", - "integrity": "sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==", - "requires": { - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/node-http-handler": "^4.0.4", - "@smithy/types": "^4.2.0", - "@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" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-waiter": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.3.tgz", - "integrity": "sha512-JtaY3FxmD+te+KSI2FJuEcfNC9T/DGGVf551babM7fAaXhjJUt7oSYurH1Devxd2+BOSUACCgt3buinx4UnmEA==", - "requires": { - "@smithy/abort-controller": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/client-s3": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.654.0.tgz", - "integrity": "sha512-EsyeZJhkZD2VMdZpNt4NhlQ3QUAF24gMC+5w2wpGg6Yw+Bv7VLdg1t3PkTQovriJX1KTJAYHcGAuy92OFmWIng==", - "requires": { - "@aws-crypto/sha1-browser": "5.2.0", - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.654.0", - "@aws-sdk/client-sts": "3.654.0", - "@aws-sdk/core": "3.654.0", - "@aws-sdk/credential-provider-node": "3.654.0", - "@aws-sdk/middleware-bucket-endpoint": "3.654.0", - "@aws-sdk/middleware-expect-continue": "3.654.0", - "@aws-sdk/middleware-flexible-checksums": "3.654.0", - "@aws-sdk/middleware-host-header": "3.654.0", - "@aws-sdk/middleware-location-constraint": "3.654.0", - "@aws-sdk/middleware-logger": "3.654.0", - "@aws-sdk/middleware-recursion-detection": "3.654.0", - "@aws-sdk/middleware-sdk-s3": "3.654.0", - "@aws-sdk/middleware-ssec": "3.654.0", - "@aws-sdk/middleware-user-agent": "3.654.0", - "@aws-sdk/region-config-resolver": "3.654.0", - "@aws-sdk/signature-v4-multi-region": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-endpoints": "3.654.0", - "@aws-sdk/util-user-agent-browser": "3.654.0", - "@aws-sdk/util-user-agent-node": "3.654.0", - "@aws-sdk/xml-builder": "3.654.0", - "@smithy/config-resolver": "^3.0.8", - "@smithy/core": "^2.4.3", - "@smithy/eventstream-serde-browser": "^3.0.9", - "@smithy/eventstream-serde-config-resolver": "^3.0.6", - "@smithy/eventstream-serde-node": "^3.0.8", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/hash-blob-browser": "^3.1.5", - "@smithy/hash-node": "^3.0.6", - "@smithy/hash-stream-node": "^3.1.5", - "@smithy/invalid-dependency": "^3.0.6", - "@smithy/md5-js": "^3.0.6", - "@smithy/middleware-content-length": "^3.0.8", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-retry": "^3.0.18", - "@smithy/middleware-serde": "^3.0.6", - "@smithy/middleware-stack": "^3.0.6", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.18", - "@smithy/util-defaults-mode-node": "^3.0.18", - "@smithy/util-endpoints": "^2.1.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-retry": "^3.0.6", - "@smithy/util-stream": "^3.1.6", - "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.5", - "tslib": "^2.6.2" - }, - "dependencies": { - "@aws-sdk/client-sso": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.654.0.tgz", - "integrity": "sha512-4kBxs2IzCDtj6a6lRXa/lXK5wWpMGzwKtb+HMXf/rJYVM6x7wYRzc1hYrOd3DYkFQ/sR3dUFj+0mTP0os3aAbA==", - "requires": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.654.0", - "@aws-sdk/middleware-host-header": "3.654.0", - "@aws-sdk/middleware-logger": "3.654.0", - "@aws-sdk/middleware-recursion-detection": "3.654.0", - "@aws-sdk/middleware-user-agent": "3.654.0", - "@aws-sdk/region-config-resolver": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-endpoints": "3.654.0", - "@aws-sdk/util-user-agent-browser": "3.654.0", - "@aws-sdk/util-user-agent-node": "3.654.0", - "@smithy/config-resolver": "^3.0.8", - "@smithy/core": "^2.4.3", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/hash-node": "^3.0.6", - "@smithy/invalid-dependency": "^3.0.6", - "@smithy/middleware-content-length": "^3.0.8", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-retry": "^3.0.18", - "@smithy/middleware-serde": "^3.0.6", - "@smithy/middleware-stack": "^3.0.6", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.18", - "@smithy/util-defaults-mode-node": "^3.0.18", - "@smithy/util-endpoints": "^2.1.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-retry": "^3.0.6", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-env": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.654.0.tgz", - "integrity": "sha512-kogsx3Ql81JouHS7DkheCDU9MYAvK0AokxjcshDveGmf7BbgbWCA8Fnb9wjQyNDaOXNvkZu8Z8rgkX91z324/w==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-http": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.654.0.tgz", - "integrity": "sha512-tgmAH4MBi/aDR882lfw48+tDV95ZH3GWc1Eoe6DpNLiM3GN2VfU/cZwuHmi6aq+vAbdIlswBHJ/+va0fOvlyjw==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/property-provider": "^3.1.6", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/util-stream": "^3.1.6", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-node": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.654.0.tgz", - "integrity": "sha512-wPV7CNYaXDEc+SS+3R0v8SZwkHRUE1z2k2j1d49tH5QBDT4tb/k2V/biXWkwSk3hbR+IMWXmuhJDv/5lybhIvg==", - "requires": { - "@aws-sdk/credential-provider-env": "3.654.0", - "@aws-sdk/credential-provider-http": "3.654.0", - "@aws-sdk/credential-provider-ini": "3.654.0", - "@aws-sdk/credential-provider-process": "3.654.0", - "@aws-sdk/credential-provider-sso": "3.654.0", - "@aws-sdk/credential-provider-web-identity": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/credential-provider-imds": "^3.2.3", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, - "dependencies": { - "@aws-sdk/credential-provider-ini": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.654.0.tgz", - "integrity": "sha512-DKSdaNu2hwdmuvnm9KnA0NLqMWxxmxSOLWjSUSoFIm++wGXUjPrRMFYKvMktaXnPuyf5my8gF/yGbwzPZ8wlTg==", - "requires": { - "@aws-sdk/credential-provider-env": "3.654.0", - "@aws-sdk/credential-provider-http": "3.654.0", - "@aws-sdk/credential-provider-process": "3.654.0", - "@aws-sdk/credential-provider-sso": "3.654.0", - "@aws-sdk/credential-provider-web-identity": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/credential-provider-imds": "^3.2.3", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-web-identity": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.654.0.tgz", - "integrity": "sha512-6a2g9gMtZToqSu+CusjNK5zvbLJahQ9di7buO3iXgbizXpLXU1rnawCpWxwslMpT5fLgMSKDnKDrr6wdEk7jSw==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/credential-provider-process": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.654.0.tgz", - "integrity": "sha512-PmQoo8sZ9Q2Ow8OMzK++Z9lI7MsRUG7sNq3E72DVA215dhtTICTDQwGlXH2AAmIp7n+G9LLRds+4wo2ehG4mkg==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-sso": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.654.0.tgz", - "integrity": "sha512-7GFme6fWEdA/XYKzZPOAdj/jS6fMBy1NdSIZsDXikS0v9jU+ZzHrAaWt13YLzHyjgxB9Sg9id9ncdY1IiubQXQ==", - "requires": { - "@aws-sdk/client-sso": "3.654.0", - "@aws-sdk/token-providers": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, - "dependencies": { - "@aws-sdk/token-providers": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.654.0.tgz", - "integrity": "sha512-D8GeJYmvbfWkQDtTB4owmIobSMexZel0fOoetwvgCQ/7L8VPph3Q2bn1TRRIXvH7wdt6DcDxA3tKMHPBkT3GlA==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - } - } - }, - "@smithy/abort-controller": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.4.tgz", - "integrity": "sha512-VupaALAQlXViW3/enTf/f5l5JZYSAxoJL7f0nanhNNKnww6DGCg1oYIuNP78KDugnkwthBO6iEcym16HhWV8RQ==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/node-http-handler": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.3.tgz", - "integrity": "sha512-/gcm5DJ3k1b1zEInzBGAZC8ntJ+jwrz1NcSIu+9dSXd1FfG0G6QgkDI40tt8/WYUbHtLyo8fEqtm2v29koWo/w==", - "requires": { - "@smithy/abort-controller": "^3.1.4", - "@smithy/protocol-http": "^4.1.3", - "@smithy/querystring-builder": "^3.0.6", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/querystring-builder": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.6.tgz", - "integrity": "sha512-sQe08RunoObe+Usujn9+R2zrLuQERi3CWvRO3BvnoWSYUaIrLKuAIeY7cMeDax6xGyfIP3x/yFWbEKSXvOnvVg==", - "requires": { - "@smithy/types": "^3.4.2", - "@smithy/util-uri-escape": "^3.0.0", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@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==", - "requires": { - "@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" - }, - "dependencies": { - "@aws-sdk/core": { - "version": "3.846.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.846.0.tgz", - "integrity": "sha512-7CX0pM906r4WSS68fCTNMTtBCSkTtf3Wggssmx13gD40gcWEZXsU00KzPp1bYheNRyPlAq3rE22xt4wLPXbuxA==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/protocol-http": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", - "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@smithy/smithy-client": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.9.tgz", - "integrity": "sha512-mbMg8mIUAWwMmb74LoYiArP04zWElPzDoA1jVOp3or0cjlDMgoS6WTC3QXK0Vxoc9I4zdrX0tq6qsOmaIoTWEQ==", - "requires": { - "@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" - } - }, - "@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@smithy/util-endpoints": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", - "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", - "requires": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-middleware": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", - "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/service-error-classification": "^4.0.6", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "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==", - "requires": { - "strnum": "^2.1.0" - } - }, - "strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==" - } - } - }, - "@aws-sdk/client-sso-oidc": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.654.0.tgz", - "integrity": "sha512-gbHrKsEnaAtmkNCVQzLyiqMzpDaThV/bWl/ODEklI+t6stW3Pe3oDMstEHLfJ6JU5g8sYnx4VLuxlnJMtUkvPw==", - "requires": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.654.0", - "@aws-sdk/credential-provider-node": "3.654.0", - "@aws-sdk/middleware-host-header": "3.654.0", - "@aws-sdk/middleware-logger": "3.654.0", - "@aws-sdk/middleware-recursion-detection": "3.654.0", - "@aws-sdk/middleware-user-agent": "3.654.0", - "@aws-sdk/region-config-resolver": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-endpoints": "3.654.0", - "@aws-sdk/util-user-agent-browser": "3.654.0", - "@aws-sdk/util-user-agent-node": "3.654.0", - "@smithy/config-resolver": "^3.0.8", - "@smithy/core": "^2.4.3", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/hash-node": "^3.0.6", - "@smithy/invalid-dependency": "^3.0.6", - "@smithy/middleware-content-length": "^3.0.8", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-retry": "^3.0.18", - "@smithy/middleware-serde": "^3.0.6", - "@smithy/middleware-stack": "^3.0.6", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.18", - "@smithy/util-defaults-mode-node": "^3.0.18", - "@smithy/util-endpoints": "^2.1.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-retry": "^3.0.6", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "dependencies": { - "@aws-sdk/client-sso": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.654.0.tgz", - "integrity": "sha512-4kBxs2IzCDtj6a6lRXa/lXK5wWpMGzwKtb+HMXf/rJYVM6x7wYRzc1hYrOd3DYkFQ/sR3dUFj+0mTP0os3aAbA==", - "requires": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.654.0", - "@aws-sdk/middleware-host-header": "3.654.0", - "@aws-sdk/middleware-logger": "3.654.0", - "@aws-sdk/middleware-recursion-detection": "3.654.0", - "@aws-sdk/middleware-user-agent": "3.654.0", - "@aws-sdk/region-config-resolver": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-endpoints": "3.654.0", - "@aws-sdk/util-user-agent-browser": "3.654.0", - "@aws-sdk/util-user-agent-node": "3.654.0", - "@smithy/config-resolver": "^3.0.8", - "@smithy/core": "^2.4.3", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/hash-node": "^3.0.6", - "@smithy/invalid-dependency": "^3.0.6", - "@smithy/middleware-content-length": "^3.0.8", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-retry": "^3.0.18", - "@smithy/middleware-serde": "^3.0.6", - "@smithy/middleware-stack": "^3.0.6", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.18", - "@smithy/util-defaults-mode-node": "^3.0.18", - "@smithy/util-endpoints": "^2.1.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-retry": "^3.0.6", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-env": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.654.0.tgz", - "integrity": "sha512-kogsx3Ql81JouHS7DkheCDU9MYAvK0AokxjcshDveGmf7BbgbWCA8Fnb9wjQyNDaOXNvkZu8Z8rgkX91z324/w==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-http": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.654.0.tgz", - "integrity": "sha512-tgmAH4MBi/aDR882lfw48+tDV95ZH3GWc1Eoe6DpNLiM3GN2VfU/cZwuHmi6aq+vAbdIlswBHJ/+va0fOvlyjw==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/property-provider": "^3.1.6", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/util-stream": "^3.1.6", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-ini": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.654.0.tgz", - "integrity": "sha512-DKSdaNu2hwdmuvnm9KnA0NLqMWxxmxSOLWjSUSoFIm++wGXUjPrRMFYKvMktaXnPuyf5my8gF/yGbwzPZ8wlTg==", - "requires": { - "@aws-sdk/credential-provider-env": "3.654.0", - "@aws-sdk/credential-provider-http": "3.654.0", - "@aws-sdk/credential-provider-process": "3.654.0", - "@aws-sdk/credential-provider-sso": "3.654.0", - "@aws-sdk/credential-provider-web-identity": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/credential-provider-imds": "^3.2.3", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-node": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.654.0.tgz", - "integrity": "sha512-wPV7CNYaXDEc+SS+3R0v8SZwkHRUE1z2k2j1d49tH5QBDT4tb/k2V/biXWkwSk3hbR+IMWXmuhJDv/5lybhIvg==", - "requires": { - "@aws-sdk/credential-provider-env": "3.654.0", - "@aws-sdk/credential-provider-http": "3.654.0", - "@aws-sdk/credential-provider-ini": "3.654.0", - "@aws-sdk/credential-provider-process": "3.654.0", - "@aws-sdk/credential-provider-sso": "3.654.0", - "@aws-sdk/credential-provider-web-identity": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/credential-provider-imds": "^3.2.3", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-process": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.654.0.tgz", - "integrity": "sha512-PmQoo8sZ9Q2Ow8OMzK++Z9lI7MsRUG7sNq3E72DVA215dhtTICTDQwGlXH2AAmIp7n+G9LLRds+4wo2ehG4mkg==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-sso": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.654.0.tgz", - "integrity": "sha512-7GFme6fWEdA/XYKzZPOAdj/jS6fMBy1NdSIZsDXikS0v9jU+ZzHrAaWt13YLzHyjgxB9Sg9id9ncdY1IiubQXQ==", - "requires": { - "@aws-sdk/client-sso": "3.654.0", - "@aws-sdk/token-providers": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-web-identity": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.654.0.tgz", - "integrity": "sha512-6a2g9gMtZToqSu+CusjNK5zvbLJahQ9di7buO3iXgbizXpLXU1rnawCpWxwslMpT5fLgMSKDnKDrr6wdEk7jSw==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/token-providers": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.654.0.tgz", - "integrity": "sha512-D8GeJYmvbfWkQDtTB4owmIobSMexZel0fOoetwvgCQ/7L8VPph3Q2bn1TRRIXvH7wdt6DcDxA3tKMHPBkT3GlA==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/abort-controller": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.4.tgz", - "integrity": "sha512-VupaALAQlXViW3/enTf/f5l5JZYSAxoJL7f0nanhNNKnww6DGCg1oYIuNP78KDugnkwthBO6iEcym16HhWV8RQ==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/node-http-handler": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.3.tgz", - "integrity": "sha512-/gcm5DJ3k1b1zEInzBGAZC8ntJ+jwrz1NcSIu+9dSXd1FfG0G6QgkDI40tt8/WYUbHtLyo8fEqtm2v29koWo/w==", - "requires": { - "@smithy/abort-controller": "^3.1.4", - "@smithy/protocol-http": "^4.1.3", - "@smithy/querystring-builder": "^3.0.6", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/querystring-builder": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.6.tgz", - "integrity": "sha512-sQe08RunoObe+Usujn9+R2zrLuQERi3CWvRO3BvnoWSYUaIrLKuAIeY7cMeDax6xGyfIP3x/yFWbEKSXvOnvVg==", - "requires": { - "@smithy/types": "^3.4.2", - "@smithy/util-uri-escape": "^3.0.0", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/client-sts": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.654.0.tgz", - "integrity": "sha512-tyHa8jsBy+/NQZFHm6Q2Q09Vi9p3EH4yPy6PU8yPewpi2klreObtrUd0anJa6nzjS9SSuqnlZWsRic3cQ4QwCg==", - "requires": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.654.0", - "@aws-sdk/core": "3.654.0", - "@aws-sdk/credential-provider-node": "3.654.0", - "@aws-sdk/middleware-host-header": "3.654.0", - "@aws-sdk/middleware-logger": "3.654.0", - "@aws-sdk/middleware-recursion-detection": "3.654.0", - "@aws-sdk/middleware-user-agent": "3.654.0", - "@aws-sdk/region-config-resolver": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-endpoints": "3.654.0", - "@aws-sdk/util-user-agent-browser": "3.654.0", - "@aws-sdk/util-user-agent-node": "3.654.0", - "@smithy/config-resolver": "^3.0.8", - "@smithy/core": "^2.4.3", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/hash-node": "^3.0.6", - "@smithy/invalid-dependency": "^3.0.6", - "@smithy/middleware-content-length": "^3.0.8", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-retry": "^3.0.18", - "@smithy/middleware-serde": "^3.0.6", - "@smithy/middleware-stack": "^3.0.6", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.18", - "@smithy/util-defaults-mode-node": "^3.0.18", - "@smithy/util-endpoints": "^2.1.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-retry": "^3.0.6", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "dependencies": { - "@aws-sdk/client-sso": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.654.0.tgz", - "integrity": "sha512-4kBxs2IzCDtj6a6lRXa/lXK5wWpMGzwKtb+HMXf/rJYVM6x7wYRzc1hYrOd3DYkFQ/sR3dUFj+0mTP0os3aAbA==", - "requires": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.654.0", - "@aws-sdk/middleware-host-header": "3.654.0", - "@aws-sdk/middleware-logger": "3.654.0", - "@aws-sdk/middleware-recursion-detection": "3.654.0", - "@aws-sdk/middleware-user-agent": "3.654.0", - "@aws-sdk/region-config-resolver": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-endpoints": "3.654.0", - "@aws-sdk/util-user-agent-browser": "3.654.0", - "@aws-sdk/util-user-agent-node": "3.654.0", - "@smithy/config-resolver": "^3.0.8", - "@smithy/core": "^2.4.3", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/hash-node": "^3.0.6", - "@smithy/invalid-dependency": "^3.0.6", - "@smithy/middleware-content-length": "^3.0.8", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-retry": "^3.0.18", - "@smithy/middleware-serde": "^3.0.6", - "@smithy/middleware-stack": "^3.0.6", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.18", - "@smithy/util-defaults-mode-node": "^3.0.18", - "@smithy/util-endpoints": "^2.1.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-retry": "^3.0.6", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-env": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.654.0.tgz", - "integrity": "sha512-kogsx3Ql81JouHS7DkheCDU9MYAvK0AokxjcshDveGmf7BbgbWCA8Fnb9wjQyNDaOXNvkZu8Z8rgkX91z324/w==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-http": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.654.0.tgz", - "integrity": "sha512-tgmAH4MBi/aDR882lfw48+tDV95ZH3GWc1Eoe6DpNLiM3GN2VfU/cZwuHmi6aq+vAbdIlswBHJ/+va0fOvlyjw==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/fetch-http-handler": "^3.2.7", - "@smithy/node-http-handler": "^3.2.2", - "@smithy/property-provider": "^3.1.6", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/util-stream": "^3.1.6", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-ini": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.654.0.tgz", - "integrity": "sha512-DKSdaNu2hwdmuvnm9KnA0NLqMWxxmxSOLWjSUSoFIm++wGXUjPrRMFYKvMktaXnPuyf5my8gF/yGbwzPZ8wlTg==", - "requires": { - "@aws-sdk/credential-provider-env": "3.654.0", - "@aws-sdk/credential-provider-http": "3.654.0", - "@aws-sdk/credential-provider-process": "3.654.0", - "@aws-sdk/credential-provider-sso": "3.654.0", - "@aws-sdk/credential-provider-web-identity": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/credential-provider-imds": "^3.2.3", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-node": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.654.0.tgz", - "integrity": "sha512-wPV7CNYaXDEc+SS+3R0v8SZwkHRUE1z2k2j1d49tH5QBDT4tb/k2V/biXWkwSk3hbR+IMWXmuhJDv/5lybhIvg==", - "requires": { - "@aws-sdk/credential-provider-env": "3.654.0", - "@aws-sdk/credential-provider-http": "3.654.0", - "@aws-sdk/credential-provider-ini": "3.654.0", - "@aws-sdk/credential-provider-process": "3.654.0", - "@aws-sdk/credential-provider-sso": "3.654.0", - "@aws-sdk/credential-provider-web-identity": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/credential-provider-imds": "^3.2.3", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-process": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.654.0.tgz", - "integrity": "sha512-PmQoo8sZ9Q2Ow8OMzK++Z9lI7MsRUG7sNq3E72DVA215dhtTICTDQwGlXH2AAmIp7n+G9LLRds+4wo2ehG4mkg==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/credential-provider-sso": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.654.0.tgz", - "integrity": "sha512-7GFme6fWEdA/XYKzZPOAdj/jS6fMBy1NdSIZsDXikS0v9jU+ZzHrAaWt13YLzHyjgxB9Sg9id9ncdY1IiubQXQ==", - "requires": { - "@aws-sdk/client-sso": "3.654.0", - "@aws-sdk/token-providers": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, - "dependencies": { - "@aws-sdk/token-providers": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.654.0.tgz", - "integrity": "sha512-D8GeJYmvbfWkQDtTB4owmIobSMexZel0fOoetwvgCQ/7L8VPph3Q2bn1TRRIXvH7wdt6DcDxA3tKMHPBkT3GlA==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/credential-provider-web-identity": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.654.0.tgz", - "integrity": "sha512-6a2g9gMtZToqSu+CusjNK5zvbLJahQ9di7buO3iXgbizXpLXU1rnawCpWxwslMpT5fLgMSKDnKDrr6wdEk7jSw==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/property-provider": "^3.1.6", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/abort-controller": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.4.tgz", - "integrity": "sha512-VupaALAQlXViW3/enTf/f5l5JZYSAxoJL7f0nanhNNKnww6DGCg1oYIuNP78KDugnkwthBO6iEcym16HhWV8RQ==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/node-http-handler": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.3.tgz", - "integrity": "sha512-/gcm5DJ3k1b1zEInzBGAZC8ntJ+jwrz1NcSIu+9dSXd1FfG0G6QgkDI40tt8/WYUbHtLyo8fEqtm2v29koWo/w==", - "requires": { - "@smithy/abort-controller": "^3.1.4", - "@smithy/protocol-http": "^4.1.3", - "@smithy/querystring-builder": "^3.0.6", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/querystring-builder": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.6.tgz", - "integrity": "sha512-sQe08RunoObe+Usujn9+R2zrLuQERi3CWvRO3BvnoWSYUaIrLKuAIeY7cMeDax6xGyfIP3x/yFWbEKSXvOnvVg==", - "requires": { - "@smithy/types": "^3.4.2", - "@smithy/util-uri-escape": "^3.0.0", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/core": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.654.0.tgz", - "integrity": "sha512-4Rwx7BVaNaFqmXBDmnOkMbyuIFFbpZ+ru4lr660p45zY1QoNNSalechfoRffcokLFOZO+VWEJkdcorPUUU993w==", - "requires": { - "@smithy/core": "^2.4.3", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/property-provider": "^3.1.6", - "@smithy/protocol-http": "^4.1.3", - "@smithy/signature-v4": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/util-middleware": "^3.0.6", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@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==", - "requires": { - "@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" - }, - "dependencies": { - "@aws-sdk/core": { - "version": "3.846.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.846.0.tgz", - "integrity": "sha512-7CX0pM906r4WSS68fCTNMTtBCSkTtf3Wggssmx13gD40gcWEZXsU00KzPp1bYheNRyPlAq3rE22xt4wLPXbuxA==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/protocol-http": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", - "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@smithy/smithy-client": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.9.tgz", - "integrity": "sha512-mbMg8mIUAWwMmb74LoYiArP04zWElPzDoA1jVOp3or0cjlDMgoS6WTC3QXK0Vxoc9I4zdrX0tq6qsOmaIoTWEQ==", - "requires": { - "@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" - } - }, - "@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-middleware": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", - "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "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==", - "requires": { - "strnum": "^2.1.0" - } - }, - "strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==" - } - } - }, - "@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==", - "requires": { - "@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" - }, - "dependencies": { - "@aws-sdk/core": { - "version": "3.846.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.846.0.tgz", - "integrity": "sha512-7CX0pM906r4WSS68fCTNMTtBCSkTtf3Wggssmx13gD40gcWEZXsU00KzPp1bYheNRyPlAq3rE22xt4wLPXbuxA==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/protocol-http": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", - "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@smithy/smithy-client": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.9.tgz", - "integrity": "sha512-mbMg8mIUAWwMmb74LoYiArP04zWElPzDoA1jVOp3or0cjlDMgoS6WTC3QXK0Vxoc9I4zdrX0tq6qsOmaIoTWEQ==", - "requires": { - "@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" - } - }, - "@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-middleware": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", - "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "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==", - "requires": { - "strnum": "^2.1.0" - } - }, - "strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==" - } - } - }, - "@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==", - "requires": { - "@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" - }, - "dependencies": { - "@aws-sdk/core": { - "version": "3.846.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.846.0.tgz", - "integrity": "sha512-7CX0pM906r4WSS68fCTNMTtBCSkTtf3Wggssmx13gD40gcWEZXsU00KzPp1bYheNRyPlAq3rE22xt4wLPXbuxA==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/protocol-http": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", - "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@smithy/smithy-client": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.9.tgz", - "integrity": "sha512-mbMg8mIUAWwMmb74LoYiArP04zWElPzDoA1jVOp3or0cjlDMgoS6WTC3QXK0Vxoc9I4zdrX0tq6qsOmaIoTWEQ==", - "requires": { - "@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" - } - }, - "@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@smithy/util-endpoints": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", - "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", - "requires": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-middleware": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", - "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/service-error-classification": "^4.0.6", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "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==", - "requires": { - "strnum": "^2.1.0" - } - }, - "strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==" - } - } - }, - "@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==", - "requires": { - "@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" - }, - "dependencies": { - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - } - } - }, - "@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==", - "requires": { - "@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" - }, - "dependencies": { - "@aws-sdk/core": { - "version": "3.846.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.846.0.tgz", - "integrity": "sha512-7CX0pM906r4WSS68fCTNMTtBCSkTtf3Wggssmx13gD40gcWEZXsU00KzPp1bYheNRyPlAq3rE22xt4wLPXbuxA==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/protocol-http": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", - "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@smithy/smithy-client": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.9.tgz", - "integrity": "sha512-mbMg8mIUAWwMmb74LoYiArP04zWElPzDoA1jVOp3or0cjlDMgoS6WTC3QXK0Vxoc9I4zdrX0tq6qsOmaIoTWEQ==", - "requires": { - "@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" - } - }, - "@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-middleware": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", - "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "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==", - "requires": { - "strnum": "^2.1.0" - } - }, - "strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==" - } - } - }, - "@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==", - "requires": { - "@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" - }, - "dependencies": { - "@aws-sdk/core": { - "version": "3.846.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.846.0.tgz", - "integrity": "sha512-7CX0pM906r4WSS68fCTNMTtBCSkTtf3Wggssmx13gD40gcWEZXsU00KzPp1bYheNRyPlAq3rE22xt4wLPXbuxA==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/protocol-http": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", - "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@smithy/smithy-client": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.9.tgz", - "integrity": "sha512-mbMg8mIUAWwMmb74LoYiArP04zWElPzDoA1jVOp3or0cjlDMgoS6WTC3QXK0Vxoc9I4zdrX0tq6qsOmaIoTWEQ==", - "requires": { - "@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" - } - }, - "@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-middleware": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", - "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "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==", - "requires": { - "strnum": "^2.1.0" - } - }, - "strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==" - } - } - }, - "@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==", - "requires": { - "@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" - }, - "dependencies": { - "@aws-sdk/core": { - "version": "3.846.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.846.0.tgz", - "integrity": "sha512-7CX0pM906r4WSS68fCTNMTtBCSkTtf3Wggssmx13gD40gcWEZXsU00KzPp1bYheNRyPlAq3rE22xt4wLPXbuxA==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/protocol-http": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", - "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@smithy/smithy-client": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.9.tgz", - "integrity": "sha512-mbMg8mIUAWwMmb74LoYiArP04zWElPzDoA1jVOp3or0cjlDMgoS6WTC3QXK0Vxoc9I4zdrX0tq6qsOmaIoTWEQ==", - "requires": { - "@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" - } - }, - "@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@smithy/util-endpoints": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", - "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", - "requires": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-middleware": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", - "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/service-error-classification": "^4.0.6", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "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==", - "requires": { - "strnum": "^2.1.0" - } - }, - "strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==" - } - } - }, - "@aws-sdk/lib-storage": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.654.0.tgz", - "integrity": "sha512-x3o11PnghWkIaC19/TYuyc0/o6jA6Oh4sa5ZPvszaaJ+NRCrN/XXrX1XlJv720X+99WN7tdz4oEcmtgQ0RaJIQ==", - "requires": { - "@smithy/abort-controller": "^3.1.4", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/smithy-client": "^3.3.2", - "buffer": "5.6.0", - "events": "3.3.0", - "stream-browserify": "3.0.0", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/abort-controller": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.4.tgz", - "integrity": "sha512-VupaALAQlXViW3/enTf/f5l5JZYSAxoJL7f0nanhNNKnww6DGCg1oYIuNP78KDugnkwthBO6iEcym16HhWV8RQ==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/middleware-bucket-endpoint": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.654.0.tgz", - "integrity": "sha512-/lWkyeLESiK+rAB4+NCw1cVPle9RN7RW/v7B4b8ORiCn1FwZLUPmEiZSYzyh4in5oa3Mri+W/g+KafZDH6LCbA==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-arn-parser": "3.568.0", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", - "@smithy/util-config-provider": "^3.0.0", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/middleware-expect-continue": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.654.0.tgz", - "integrity": "sha512-S7fSlo8vdjkQTy9DmdF54ZsPwc+aA4z5Y9JVqAlGL9QiZe/fPtRE3GZ8BBbMICjBfMEa12tWjzhDz9su2c6PIA==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/middleware-flexible-checksums": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.654.0.tgz", - "integrity": "sha512-ZSRC+Lf9WxyoDLuTkd7JrFRrBLPLXcTOZzX6tDsnHc6tgdneBNwV3/ZOYUwQ8bdwLLnzSaQUU+X5B2BkEFKIhQ==", - "requires": { - "@aws-crypto/crc32": "5.2.0", - "@aws-crypto/crc32c": "5.2.0", - "@aws-sdk/types": "3.654.0", - "@smithy/is-array-buffer": "^3.0.0", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/middleware-host-header": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.654.0.tgz", - "integrity": "sha512-rxGgVHWKp8U2ubMv+t+vlIk7QYUaRCHaVpmUlJv0Wv6Q0KeO9a42T9FxHphjOTlCGQOLcjCreL9CF8Qhtb4mdQ==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/middleware-location-constraint": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.654.0.tgz", - "integrity": "sha512-Duvv5c4DEQ7P6c0YlcvEUW3xCJi6X2uktafNGjILhVDMQwShSF/aFqNv/ikWU/luQcmWHZ9DtDjTR9UKLh6eTA==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/middleware-logger": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.654.0.tgz", - "integrity": "sha512-OQYb+nWlmASyXfRb989pwkJ9EVUMP1CrKn2eyTk3usl20JZmKo2Vjis6I0tLUkMSxMhnBJJlQKyWkRpD/u1FVg==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/middleware-recursion-detection": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.654.0.tgz", - "integrity": "sha512-gKSomgltKVmsT8sC6W7CrADZ4GHwX9epk3GcH6QhebVO3LA9LRbkL3TwOPUXakxxOLLUTYdOZLIOtFf7iH00lg==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/middleware-sdk-s3": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.654.0.tgz", - "integrity": "sha512-6prq+GK6hLMAbxEb83tBMb1YiTWWK196fJhFO/7gE5TUPL1v756RhQZzKV/njbwB1fIBjRBTuhYLh5Bn98HhdA==", - "requires": { - "@aws-sdk/core": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-arn-parser": "3.568.0", - "@smithy/core": "^2.4.3", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.3", - "@smithy/signature-v4": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-stream": "^3.1.6", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/middleware-ssec": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.654.0.tgz", - "integrity": "sha512-k7hkQDJh4hcRJC7YojQ11kc37SY4foryen26Eafj5qYjeG2OGMW0oZTJDl1TVFJ7AcCjqIuMIo0Ho2US/2JspQ==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/middleware-user-agent": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.654.0.tgz", - "integrity": "sha512-liCcqPAyRsr53cy2tYu4qeH4MMN0eh9g6k56XzI5xd4SghXH5YWh4qOYAlQ8T66ZV4nPMtD8GLtLXGzsH8moFg==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-endpoints": "3.654.0", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/nested-clients": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.787.0.tgz", - "integrity": "sha512-xk03q1xpKNHgbuo+trEf1dFrI239kuMmjKKsqLEsHlAZbuFq4yRGMlHBrVMnKYOPBhVFDS/VineM991XI52fKg==", - "requires": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.775.0", - "@aws-sdk/middleware-host-header": "3.775.0", - "@aws-sdk/middleware-logger": "3.775.0", - "@aws-sdk/middleware-recursion-detection": "3.775.0", - "@aws-sdk/middleware-user-agent": "3.787.0", - "@aws-sdk/region-config-resolver": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@aws-sdk/util-endpoints": "3.787.0", - "@aws-sdk/util-user-agent-browser": "3.775.0", - "@aws-sdk/util-user-agent-node": "3.787.0", - "@smithy/config-resolver": "^4.1.0", - "@smithy/core": "^3.2.0", - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/hash-node": "^4.0.2", - "@smithy/invalid-dependency": "^4.0.2", - "@smithy/middleware-content-length": "^4.0.2", - "@smithy/middleware-endpoint": "^4.1.0", - "@smithy/middleware-retry": "^4.1.0", - "@smithy/middleware-serde": "^4.0.3", - "@smithy/middleware-stack": "^4.0.2", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/node-http-handler": "^4.0.4", - "@smithy/protocol-http": "^5.1.0", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "@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.8", - "@smithy/util-defaults-mode-node": "^4.0.8", - "@smithy/util-endpoints": "^3.0.2", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-retry": "^4.0.2", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "dependencies": { - "@aws-sdk/core": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.775.0.tgz", - "integrity": "sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA==", - "requires": { - "@aws-sdk/types": "3.775.0", - "@smithy/core": "^3.2.0", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/signature-v4": "^5.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/util-middleware": "^4.0.2", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-host-header": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.775.0.tgz", - "integrity": "sha512-tkSegM0Z6WMXpLB8oPys/d+umYIocvO298mGvcMCncpRl77L9XkvSLJIFzaHes+o7djAgIduYw8wKIMStFss2w==", - "requires": { - "@aws-sdk/types": "3.775.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-logger": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.775.0.tgz", - "integrity": "sha512-FaxO1xom4MAoUJsldmR92nT1G6uZxTdNYOFYtdHfd6N2wcNaTuxgjIvqzg5y7QIH9kn58XX/dzf1iTjgqUStZw==", - "requires": { - "@aws-sdk/types": "3.775.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-recursion-detection": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.775.0.tgz", - "integrity": "sha512-GLCzC8D0A0YDG5u3F5U03Vb9j5tcOEFhr8oc6PDk0k0vm5VwtZOE6LvK7hcCSoAB4HXyOUM0sQuXrbaAh9OwXA==", - "requires": { - "@aws-sdk/types": "3.775.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/middleware-user-agent": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.787.0.tgz", - "integrity": "sha512-Lnfj8SmPLYtrDFthNIaNj66zZsBCam+E4XiUDr55DIHTGstH6qZ/q6vg0GfbukxwSmUcGMwSR4Qbn8rb8yd77g==", - "requires": { - "@aws-sdk/core": "3.775.0", - "@aws-sdk/types": "3.775.0", - "@aws-sdk/util-endpoints": "3.787.0", - "@smithy/core": "^3.2.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/region-config-resolver": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.775.0.tgz", - "integrity": "sha512-40iH3LJjrQS3LKUJAl7Wj0bln7RFPEvUYKFxtP8a+oKFDO0F65F52xZxIJbPn6sHkxWDAnZlGgdjZXM3p2g5wQ==", - "requires": { - "@aws-sdk/types": "3.775.0", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/types": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.775.0.tgz", - "integrity": "sha512-ZoGKwa4C9fC9Av6bdfqcW6Ix5ot05F/S4VxWR2nHuMv7hzfmAjTOcUiWT7UR4hM/U0whf84VhDtXN/DWAk52KA==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/util-endpoints": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.787.0.tgz", - "integrity": "sha512-fd3zkiOkwnbdbN0Xp9TsP5SWrmv0SpT70YEdbb8wAj2DWQwiCmFszaSs+YCvhoCdmlR3Wl9Spu0pGpSAGKeYvQ==", - "requires": { - "@aws-sdk/types": "3.775.0", - "@smithy/types": "^4.2.0", - "@smithy/util-endpoints": "^3.0.2", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/util-user-agent-browser": { - "version": "3.775.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.775.0.tgz", - "integrity": "sha512-txw2wkiJmZKVdDbscK7VBK+u+TJnRtlUjRTLei+elZg2ADhpQxfVAQl436FUeIv6AhB/oRHW6/K/EAGXUSWi0A==", - "requires": { - "@aws-sdk/types": "3.775.0", - "@smithy/types": "^4.2.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "@aws-sdk/util-user-agent-node": { - "version": "3.787.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.787.0.tgz", - "integrity": "sha512-mG7Lz8ydfG4SF9e8WSXiPQ/Lsn3n8A5B5jtPROidafi06I3ckV2WxyMLdwG14m919NoS6IOfWHyRGSqWIwbVKA==", - "requires": { - "@aws-sdk/middleware-user-agent": "3.787.0", - "@aws-sdk/types": "3.775.0", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/abort-controller": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.2.tgz", - "integrity": "sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/config-resolver": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.0.tgz", - "integrity": "sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A==", - "requires": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "tslib": "^2.6.2" - } - }, - "@smithy/core": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.2.0.tgz", - "integrity": "sha512-k17bgQhVZ7YmUvA8at4af1TDpl0NDMBuBKJl8Yg0nrefwmValU+CnA5l/AriVdQNthU/33H3nK71HrLgqOPr1Q==", - "requires": { - "@smithy/middleware-serde": "^4.0.3", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-stream": "^4.2.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@smithy/credential-provider-imds": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.2.tgz", - "integrity": "sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w==", - "requires": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "tslib": "^2.6.2" - } - }, - "@smithy/fetch-http-handler": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.2.tgz", - "integrity": "sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==", - "requires": { - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/util-base64": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@smithy/hash-node": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.2.tgz", - "integrity": "sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==", - "requires": { - "@smithy/types": "^4.2.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@smithy/invalid-dependency": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.2.tgz", - "integrity": "sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/middleware-content-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.2.tgz", - "integrity": "sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==", - "requires": { - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/middleware-endpoint": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.0.tgz", - "integrity": "sha512-xhLimgNCbCzsUppRTGXWkZywksuTThxaIB0HwbpsVLY5sceac4e1TZ/WKYqufQLaUy+gUSJGNdwD2jo3cXL0iA==", - "requires": { - "@smithy/core": "^3.2.0", - "@smithy/middleware-serde": "^4.0.3", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "@smithy/util-middleware": "^4.0.2", - "tslib": "^2.6.2" - } - }, - "@smithy/middleware-retry": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.0.tgz", - "integrity": "sha512-2zAagd1s6hAaI/ap6SXi5T3dDwBOczOMCSkkYzktqN1+tzbk1GAsHNAdo/1uzxz3Ky02jvZQwbi/vmDA6z4Oyg==", - "requires": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/service-error-classification": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-retry": "^4.0.2", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - } - }, - "@smithy/middleware-serde": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.3.tgz", - "integrity": "sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/middleware-stack": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.2.tgz", - "integrity": "sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/node-config-provider": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.2.tgz", - "integrity": "sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw==", - "requires": { - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/node-http-handler": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.4.tgz", - "integrity": "sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==", - "requires": { - "@smithy/abort-controller": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/property-provider": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.2.tgz", - "integrity": "sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/protocol-http": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.0.tgz", - "integrity": "sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/querystring-builder": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.2.tgz", - "integrity": "sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==", - "requires": { - "@smithy/types": "^4.2.0", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@smithy/querystring-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.2.tgz", - "integrity": "sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/service-error-classification": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.2.tgz", - "integrity": "sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ==", - "requires": { - "@smithy/types": "^4.2.0" - } - }, - "@smithy/shared-ini-file-loader": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.2.tgz", - "integrity": "sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/signature-v4": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.0.tgz", - "integrity": "sha512-4t5WX60sL3zGJF/CtZsUQTs3UrZEDO2P7pEaElrekbLqkWPYkgqNW1oeiNYC6xXifBnT9dVBOnNQRvOE9riU9w==", - "requires": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@smithy/smithy-client": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.2.0.tgz", - "integrity": "sha512-Qs65/w30pWV7LSFAez9DKy0Koaoh3iHhpcpCCJ4waj/iqwsuSzJna2+vYwq46yBaqO5ZbP9TjUsATUNxrKeBdw==", - "requires": { - "@smithy/core": "^3.2.0", - "@smithy/middleware-endpoint": "^4.1.0", - "@smithy/middleware-stack": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "@smithy/util-stream": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/url-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.2.tgz", - "integrity": "sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==", - "requires": { - "@smithy/querystring-parser": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-defaults-mode-browser": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.8.tgz", - "integrity": "sha512-ZTypzBra+lI/LfTYZeop9UjoJhhGRTg3pxrNpfSTQLd3AJ37r2z4AXTKpq1rFXiiUIJsYyFgNJdjWRGP/cbBaQ==", - "requires": { - "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-defaults-mode-node": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.8.tgz", - "integrity": "sha512-Rgk0Jc/UDfRTzVthye/k2dDsz5Xxs9LZaKCNPgJTRyoyBoeiNCnHsYGOyu1PKN+sDyPnJzMOz22JbwxzBp9NNA==", - "requires": { - "@smithy/config-resolver": "^4.1.0", - "@smithy/credential-provider-imds": "^4.0.2", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-endpoints": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.2.tgz", - "integrity": "sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ==", - "requires": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@smithy/util-middleware": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.2.tgz", - "integrity": "sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==", - "requires": { - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-retry": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.2.tgz", - "integrity": "sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg==", - "requires": { - "@smithy/service-error-classification": "^4.0.2", - "@smithy/types": "^4.2.0", - "tslib": "^2.6.2" - } - }, - "@smithy/util-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.0.tgz", - "integrity": "sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==", - "requires": { - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/node-http-handler": "^4.0.4", - "@smithy/types": "^4.2.0", - "@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" - } - }, - "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/region-config-resolver": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.654.0.tgz", - "integrity": "sha512-ydGOrXJxj3x0sJhsXyTmvJVLAE0xxuTWFJihTl67RtaO7VRNtd82I3P3bwoMMaDn5WpmV5mPo8fEUDRlBm3fPg==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/types": "^3.4.2", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.6", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/s3-presigned-post": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-presigned-post/-/s3-presigned-post-3.654.0.tgz", - "integrity": "sha512-MKpoLK4FV5/8Oydm3FQiSCbP6HoI75k2Z8z8MVK9bTiObedAMXTkdZDzq2qtqngEM4Lb1GgyJkActihJcv3AYw==", - "dev": true, - "requires": { - "@aws-sdk/client-s3": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-format-url": "3.654.0", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/signature-v4": "^4.1.3", - "@smithy/types": "^3.4.2", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", - "dev": true, - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/s3-request-presigner": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.654.0.tgz", - "integrity": "sha512-se1DllTTkaB85RSB60U/VUq5rCzwhqYZudxrf1zlWD0YjZpwKqifWgBomd3AyPZtQRQOcQooBcmZCVfGfdAuJQ==", - "requires": { - "@aws-sdk/signature-v4-multi-region": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@aws-sdk/util-format-url": "3.654.0", - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.2", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/signature-v4-multi-region": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.654.0.tgz", - "integrity": "sha512-f8kyvbzgD3lSK1kFc3jsDCYjdutcqGO3tOzYO/QIK7BTl5lxc4rm6IKTcF2UYJsn8jiNqih7tVK8aVIGi8IF/w==", + "@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==", "requires": { - "@aws-sdk/middleware-sdk-s3": "3.654.0", - "@aws-sdk/types": "3.654.0", - "@smithy/protocol-http": "^4.1.3", - "@smithy/signature-v4": "^4.1.3", - "@smithy/types": "^3.4.2", + "@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" }, "dependencies": { - "@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", - "requires": { - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - } - }, - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", - "requires": { - "tslib": "^2.6.2" - } - } - } - }, - "@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==", - "requires": { - "@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" - }, - "dependencies": { - "@aws-sdk/core": { - "version": "3.846.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.846.0.tgz", - "integrity": "sha512-7CX0pM906r4WSS68fCTNMTtBCSkTtf3Wggssmx13gD40gcWEZXsU00KzPp1bYheNRyPlAq3rE22xt4wLPXbuxA==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "@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==", "requires": { - "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, - "@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==", + "@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==", "requires": { - "@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" - } - }, - "@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", - "requires": { - "@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", + "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, - "@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==", + "@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==", "requires": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", + "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } - }, - "@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==", + } + } + }, + "@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==", + "requires": { + "@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" + }, + "dependencies": { + "@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==", "requires": { - "@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" } }, - "@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==", + "@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==", "requires": { - "@smithy/types": "^4.3.1", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, - "@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==", + "@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==", "requires": { - "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } - }, + } + } + }, + "@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==", + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "@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==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "requires": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { "@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==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", + "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==", "requires": { - "@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" } }, - "@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==", - "requires": { - "@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" - } - }, - "@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==", + "@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==", "requires": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", + "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, - "@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==", + "@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==", "requires": { - "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } - }, - "@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==", + } + } + }, + "@aws-sdk/client-ecs": { + "version": "3.1023.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.1023.0.tgz", + "integrity": "sha512-+EYwDGPS21xyatq1xTXlmmyKKkfk7lhAc+QlMznKnuM5mytM4ZzFAgp93NrgIemOqqervH5sbNDrKwurG8NAFA==", + "requires": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/credential-provider-node": "^3.972.29", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.9", + "@aws-sdk/middleware-user-agent": "^3.972.28", + "@aws-sdk/region-config-resolver": "^3.972.10", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.14", + "@smithy/config-resolver": "^4.4.13", + "@smithy/core": "^3.23.13", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/middleware-retry": "^4.4.46", + "@smithy/middleware-serde": "^4.2.16", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.1", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.44", + "@smithy/util-defaults-mode-node": "^4.2.48", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.13", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.14", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", "requires": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "@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==", - "requires": { - "@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" - } - }, - "@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==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.1.tgz", + "integrity": "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==", "requires": { - "@smithy/types": "^4.3.1", + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "@smithy/protocol-http": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", - "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "requires": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "@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==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", "requires": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1" - } - }, - "@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==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@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" - } - }, - "@smithy/smithy-client": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.9.tgz", - "integrity": "sha512-mbMg8mIUAWwMmb74LoYiArP04zWElPzDoA1jVOp3or0cjlDMgoS6WTC3QXK0Vxoc9I4zdrX0tq6qsOmaIoTWEQ==", - "requires": { - "@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", + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } }, - "@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==", + "@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", "requires": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", + "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, - "@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", "requires": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, - "@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==", + "@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", "requires": { "tslib": "^2.6.2" } }, - "@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==", + "@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", "requires": { + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } - }, - "@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==", + } + } + }, + "@aws-sdk/client-s3": { + "version": "3.1023.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1023.0.tgz", + "integrity": "sha512-IvNy49sdoCWd3fgHQxail3y0UQdfKj1Xk0VPu9HTwlog60o9Lmp5ykjZ2LlIuHEPaxq4Siih707GB/ulUWgetw==", + "requires": { + "@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.973.26", + "@aws-sdk/credential-provider-node": "^3.972.29", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.8", + "@aws-sdk/middleware-expect-continue": "^3.972.8", + "@aws-sdk/middleware-flexible-checksums": "^3.974.6", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-location-constraint": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.9", + "@aws-sdk/middleware-sdk-s3": "^3.972.27", + "@aws-sdk/middleware-ssec": "^3.972.8", + "@aws-sdk/middleware-user-agent": "^3.972.28", + "@aws-sdk/region-config-resolver": "^3.972.10", + "@aws-sdk/signature-v4-multi-region": "^3.996.15", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.14", + "@smithy/config-resolver": "^4.4.13", + "@smithy/core": "^3.23.13", + "@smithy/eventstream-serde-browser": "^4.2.12", + "@smithy/eventstream-serde-config-resolver": "^4.3.12", + "@smithy/eventstream-serde-node": "^4.2.12", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-blob-browser": "^4.2.13", + "@smithy/hash-node": "^4.2.12", + "@smithy/hash-stream-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/md5-js": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/middleware-retry": "^4.4.46", + "@smithy/middleware-serde": "^4.2.16", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.1", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.44", + "@smithy/util-defaults-mode-node": "^4.2.48", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.13", + "@smithy/util-stream": "^4.5.21", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.14", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", "requires": { - "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" } }, - "@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==", + "@smithy/node-http-handler": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.1.tgz", + "integrity": "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==", "requires": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, - "@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==", + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "requires": { - "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, - "@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==", + "@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", "requires": { - "@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", + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, - "@smithy/util-endpoints": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", - "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, - "@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==", + "@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", "requires": { + "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "@smithy/util-middleware": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", - "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", - "requires": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", - "requires": { - "@smithy/service-error-classification": "^4.0.6", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - } - }, - "@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==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", "requires": { - "@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", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "@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==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", "requires": { "tslib": "^2.6.2" } }, "@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==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", "requires": { - "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } - }, - "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==", - "requires": { - "strnum": "^2.1.0" - } - }, - "strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==" } } }, - "@aws-sdk/types": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.654.0.tgz", - "integrity": "sha512-VWvbED3SV+10QJIcmU/PKjsKilsTV16d1I7/on4bvD/jo1qGeMXqLDBSen3ks/tuvXZF/mFc7ZW/W2DiLVtO7A==", + "@aws-sdk/client-s3vectors": { + "version": "3.1023.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3vectors/-/client-s3vectors-3.1023.0.tgz", + "integrity": "sha512-lOlRAWrfhK+STDdj6goo+Ysg4L5N+KPjeBEO57JeQaCysEQt/aSy0spIOpGaE8Nbd9sIo/bflZataKfKgnigzQ==", "requires": { - "@smithy/types": "^3.4.2", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/credential-provider-node": "^3.972.29", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.9", + "@aws-sdk/middleware-user-agent": "^3.972.28", + "@aws-sdk/region-config-resolver": "^3.972.10", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.14", + "@smithy/config-resolver": "^4.4.13", + "@smithy/core": "^3.23.13", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/middleware-retry": "^4.4.46", + "@smithy/middleware-serde": "^4.2.16", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.1", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.44", + "@smithy/util-defaults-mode-node": "^4.2.48", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.13", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "dependencies": { - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", "requires": { "tslib": "^2.6.2" } - } - } - }, - "@aws-sdk/util-arn-parser": { - "version": "3.568.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.568.0.tgz", - "integrity": "sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@aws-sdk/util-endpoints": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.654.0.tgz", - "integrity": "sha512-i902fcBknHs0Irgdpi62+QMvzxE+bczvILXigYrlHL4+PiEnlMVpni5L5W1qCkNZXf8AaMrSBuR1NZAGp6UOUw==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/types": "^3.4.2", - "@smithy/util-endpoints": "^2.1.2", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + }, + "@smithy/node-http-handler": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.1.tgz", + "integrity": "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==", "requires": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } - } - } - }, - "@aws-sdk/util-format-url": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.654.0.tgz", - "integrity": "sha512-2yAlJ/l1uTJhS52iu4+/EvdIyQhDBL+nATY8rEjFI0H+BHGVrJIH2CL4DByhvi2yvYwsqQX0HYah6pF/yoXukA==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/querystring-builder": "^3.0.6", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, - "dependencies": { + }, + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, "@smithy/querystring-builder": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.6.tgz", - "integrity": "sha512-sQe08RunoObe+Usujn9+R2zrLuQERi3CWvRO3BvnoWSYUaIrLKuAIeY7cMeDax6xGyfIP3x/yFWbEKSXvOnvVg==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", "requires": { - "@smithy/types": "^3.4.2", - "@smithy/util-uri-escape": "^3.0.0", + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } }, - "@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", "requires": { + "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" - } - } - } - }, - "@aws-sdk/util-locate-window": { - "version": "3.568.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", - "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", - "requires": { - "tslib": "^2.6.2" - } - }, - "@aws-sdk/util-user-agent-browser": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.654.0.tgz", - "integrity": "sha512-ykYAJqvnxLt7wfrqya28wuH3/7NdrwzfiFd7NqEVQf7dXVxL5RPEpD7DxjcyQo3DsHvvdUvGZVaQhozycn1pzA==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/types": "^3.4.2", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + } + }, + "@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", "requires": { + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } - } - } - }, - "@aws-sdk/util-user-agent-node": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.654.0.tgz", - "integrity": "sha512-a0ojjdBN6pqv6gB4H/QPPSfhs7mFtlVwnmKCM/QrTaFzN0U810PJ1BST3lBx5sa23I5jWHGaoFY+5q65C3clLQ==", - "requires": { - "@aws-sdk/types": "3.654.0", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/types": "^3.4.2", - "tslib": "^2.6.2" - }, - "dependencies": { - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + }, + "@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", "requires": { + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } } } }, - "@aws-sdk/xml-builder": { - "version": "3.654.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.654.0.tgz", - "integrity": "sha512-qA2diK3d/ztC8HUb7NwPKbJRV01NpzTzxFn+L5G3HzJBNeKbjLcprQ/9uG9gp2UEx2Go782FI1ddrMNa0qBICA==", + "@aws-sdk/core": { + "version": "3.973.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.26.tgz", + "integrity": "sha512-A/E6n2W42ruU+sfWk+mMUOyVXbsSgGrY3MJ9/0Az5qUdG67y8I6HYzzoAa+e/lzxxl1uCYmEL6BTMi9ZiZnplQ==", "requires": { - "@smithy/types": "^3.4.2", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/xml-builder": "^3.972.16", + "@smithy/core": "^3.23.13", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/signature-v4": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "dependencies": { - "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", "requires": { "tslib": "^2.6.2" } - } - } - }, - "@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.25.9", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - } - }, - "@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", - "dev": true - }, - "@babel/core": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", - "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.10", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.10", - "@babel/parser": "^7.26.10", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.10", - "@babel/types": "^7.26.10", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", - "dev": true, - "requires": { - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, + }, + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "requires": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" } - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "requires": { - "@babel/types": "^7.25.9" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", - "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.26.8", - "@babel/helper-validator-option": "^7.25.9", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "dependencies": { - "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, + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { - "yallist": "^3.0.2" + "tslib": "^2.6.2" } }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true + "@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "requires": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + } }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "requires": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + } } } }, - "@babel/helper-create-class-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", - "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", - "dev": true, + "@aws-sdk/crc64-nvme": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.5.tgz", + "integrity": "sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg==", "requires": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.27.0", - "semver": "^6.3.1" + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" }, "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } } } }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.0.tgz", - "integrity": "sha512-fO8l08T76v48BhpNRW/nQ0MxfnSdoSKUJBMjubOAYffsVuGG5qOfMq7N6Es7UJvi7Y8goXXo07EfcHZXDPuELQ==", - "dev": true, + "@aws-sdk/credential-provider-env": { + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.24.tgz", + "integrity": "sha512-FWg8uFmT6vQM7VuzELzwVo5bzExGaKHdubn0StjgrcU5FvuLExUe+k06kn/40uKv59rYzhez8eFNM4yYE/Yb/w==", "requires": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "regexpu-core": "^6.2.0", - "semver": "^6.3.1" + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", - "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", - "dev": true, - "requires": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - } - }, - "@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "dev": true, - "requires": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - } - }, - "@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", - "dev": true, - "requires": { - "@babel/types": "^7.25.9" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", - "dev": true - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", - "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-wrap-function": "^7.25.9", - "@babel/traverse": "^7.25.9" - } - }, - "@babel/helper-replace-supers": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", - "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.26.5" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", - "dev": true, - "requires": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - } - }, - "@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", - "dev": true - }, - "@babel/helper-wrap-function": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", - "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", - "dev": true, - "requires": { - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - } - }, - "@babel/helpers": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", - "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", - "dev": true, - "requires": { - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0" - } - }, - "@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", - "dev": true, - "requires": { - "@babel/types": "^7.27.0" - } - }, - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", - "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" - } - }, - "@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", - "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9" - } - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", - "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9" - } - }, - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", - "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "requires": {} - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", - "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9" - } - }, - "@babel/plugin-syntax-import-attributes": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", - "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", - "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", - "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9" - } - }, - "@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", - "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9" - } - }, - "@babel/plugin-transform-async-generator-functions": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", - "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-remap-async-to-generator": "^7.25.9", - "@babel/traverse": "^7.26.8" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", - "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9" + "dependencies": { + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", - "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", - "dev": true, + "@aws-sdk/credential-provider-http": { + "version": "3.972.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.26.tgz", + "integrity": "sha512-CY4ppZ+qHYqcXqBVi//sdHST1QK3KzOEiLtpLsc9W2k2vfZPKExGaQIsOwcyvjpjUEolotitmd3mUNY56IwDEA==", "requires": { - "@babel/helper-plugin-utils": "^7.26.5" + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/types": "^3.973.6", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/node-http-handler": "^4.5.1", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/util-stream": "^4.5.21", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/node-http-handler": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.1.tgz", + "integrity": "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==", + "requires": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", + "requires": { + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-block-scoping": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.0.tgz", - "integrity": "sha512-u1jGphZ8uDI2Pj/HJj6YQ6XQLZCNjOlprjxB5SVz6rq2T6SwAR+CdrWK0CP7F+9rDVMXdB0+r6Am5G5aobOjAQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.26.5" + "@aws-sdk/credential-provider-ini": { + "version": "3.972.28", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.28.tgz", + "integrity": "sha512-wXYvq3+uQcZV7k+bE4yDXCTBdzWTU9x/nMiKBfzInmv6yYK1veMK0AKvRfRBd72nGWYKcL6AxwiPg9z/pYlgpw==", + "requires": { + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/credential-provider-env": "^3.972.24", + "@aws-sdk/credential-provider-http": "^3.972.26", + "@aws-sdk/credential-provider-login": "^3.972.28", + "@aws-sdk/credential-provider-process": "^3.972.24", + "@aws-sdk/credential-provider-sso": "^3.972.28", + "@aws-sdk/credential-provider-web-identity": "^3.972.28", + "@aws-sdk/nested-clients": "^3.996.18", + "@aws-sdk/types": "^3.973.6", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-class-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", - "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", - "dev": true, + "@aws-sdk/credential-provider-login": { + "version": "3.972.28", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.28.tgz", + "integrity": "sha512-ZSTfO6jqUTCysbdBPtEX5OUR//3rbD0lN7jO3sQeS2Gjr/Y+DT6SbIJ0oT2cemNw3UzKu97sNONd1CwNMthuZQ==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/nested-clients": "^3.996.18", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-class-static-block": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", - "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", - "dev": true, + "@aws-sdk/credential-provider-node": { + "version": "3.972.29", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.29.tgz", + "integrity": "sha512-clSzDcvndpFJAggLDnDb36sPdlZYyEs5Zm6zgZjjUhwsJgSWiWKwFIXUVBcbruidNyBdbpOv2tNDL9sX8y3/0g==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/credential-provider-env": "^3.972.24", + "@aws-sdk/credential-provider-http": "^3.972.26", + "@aws-sdk/credential-provider-ini": "^3.972.28", + "@aws-sdk/credential-provider-process": "^3.972.24", + "@aws-sdk/credential-provider-sso": "^3.972.28", + "@aws-sdk/credential-provider-web-identity": "^3.972.28", + "@aws-sdk/types": "^3.973.6", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-classes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", - "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", - "dev": true, + "@aws-sdk/credential-provider-process": { + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.24.tgz", + "integrity": "sha512-Q2k/XLrFXhEztPHqj4SLCNID3hEPdlhh1CDLBpNnM+1L8fq7P+yON9/9M1IGN/dA5W45v44ylERfXtDAlmMNmw==", "requires": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "globals": "^11.1.0" + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" }, "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } } } }, - "@babel/plugin-transform-computed-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", - "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", - "dev": true, + "@aws-sdk/credential-provider-sso": { + "version": "3.972.28", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.28.tgz", + "integrity": "sha512-IoUlmKMLEITFn1SiCTjPfR6KrE799FBo5baWyk/5Ppar2yXZoUdaRqZzJzK6TcJxx450M8m8DbpddRVYlp5R/A==", "requires": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/template": "^7.25.9" + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/nested-clients": "^3.996.18", + "@aws-sdk/token-providers": "3.1021.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-destructuring": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", - "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", - "dev": true, + "@aws-sdk/credential-provider-web-identity": { + "version": "3.972.28", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.28.tgz", + "integrity": "sha512-d+6h0SD8GGERzKe27v5rOzNGKOl0D+l0bWJdqrxH8WSQzHzjsQFIAPgIeOTUwBHVsKKwtSxc91K/SWax6XgswQ==", "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/nested-clients": "^3.996.18", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", - "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/lib-storage": { + "version": "3.1023.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.1023.0.tgz", + "integrity": "sha512-1SFnmHlkKQgQxAt7/nK2f7b90kmymceojIbZT+yoSlHh2rJk2Dcjld8zo6lwUdfROrMwi4PP+z5nRMPG+d7zjQ==", + "requires": { + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "buffer": "5.6.0", + "events": "3.3.0", + "stream-browserify": "3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", - "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", - "dev": true, + "@aws-sdk/middleware-bucket-endpoint": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.8.tgz", + "integrity": "sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw==", "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", - "dev": true, + "@aws-sdk/middleware-expect-continue": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.8.tgz", + "integrity": "sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/types": "^3.973.6", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-dynamic-import": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", - "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", - "dev": true, + "@aws-sdk/middleware-flexible-checksums": { + "version": "3.974.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.6.tgz", + "integrity": "sha512-YckB8k1ejbyCg/g36gUMFLNzE4W5cERIa4MtsdO+wpTmJEP0+TB7okWIt7d8TDOvnb7SwvxJ21E4TGOBxFpSWQ==", "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/crc64-nvme": "^3.972.5", + "@aws-sdk/types": "^3.973.6", + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-stream": "^4.5.21", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "requires": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "requires": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", - "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", - "dev": true, + "@aws-sdk/middleware-host-header": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.8.tgz", + "integrity": "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==", "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/types": "^3.973.6", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-export-namespace-from": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", - "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", - "dev": true, + "@aws-sdk/middleware-location-constraint": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.8.tgz", + "integrity": "sha512-KaUoFuoFPziIa98DSQsTPeke1gvGXlc5ZGMhy+b+nLxZ4A7jmJgLzjEF95l8aOQN2T/qlPP3MrAyELm8ExXucw==", "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-for-of": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz", - "integrity": "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==", - "dev": true, + "@aws-sdk/middleware-logger": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.8.tgz", + "integrity": "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==", "requires": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-function-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", - "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", - "dev": true, + "@aws-sdk/middleware-recursion-detection": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.9.tgz", + "integrity": "sha512-/Wt5+CT8dpTFQxEJ9iGy/UGrXr7p2wlIOEHvIr/YcHYByzoLjrqkYqXdJjd9UIgWjv7eqV2HnFJen93UTuwfTQ==", "requires": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@aws-sdk/types": "^3.973.6", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-json-strings": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", - "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/middleware-sdk-s3": { + "version": "3.972.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.27.tgz", + "integrity": "sha512-gomO6DZwx+1D/9mbCpcqO5tPBqYBK7DtdgjTIjZ4yvfh/S7ETwAPS0XbJgP2JD8Ycr5CwVrEkV1sFtu3ShXeOw==", + "requires": { + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/core": "^3.23.13", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/signature-v4": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-stream": "^4.5.21", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "requires": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "requires": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", - "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", - "dev": true, + "@aws-sdk/middleware-ssec": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.8.tgz", + "integrity": "sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw==", "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-logical-assignment-operators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", - "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", - "dev": true, + "@aws-sdk/middleware-user-agent": { + "version": "3.972.28", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.28.tgz", + "integrity": "sha512-cfWZFlVh7Va9lRay4PN2A9ARFzaBYcA097InT5M2CdRS05ECF5yaz86jET8Wsl2WcyKYEvVr/QNmKtYtafUHtQ==", "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@smithy/core": "^3.23.13", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-retry": "^4.2.13", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", - "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", - "dev": true, + "@aws-sdk/nested-clients": { + "version": "3.996.18", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.18.tgz", + "integrity": "sha512-c7ZSIXrESxHKx2Mcopgd8AlzZgoXMr20fkx5ViPWPOLBvmyhw9VwJx/Govg8Ef/IhEon5R9l53Z8fdYSEmp6VA==", "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.9", + "@aws-sdk/middleware-user-agent": "^3.972.28", + "@aws-sdk/region-config-resolver": "^3.972.10", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.14", + "@smithy/config-resolver": "^4.4.13", + "@smithy/core": "^3.23.13", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/middleware-retry": "^4.4.46", + "@smithy/middleware-serde": "^4.2.16", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.1", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.44", + "@smithy/util-defaults-mode-node": "^4.2.48", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.13", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/node-http-handler": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.1.tgz", + "integrity": "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==", + "requires": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", + "requires": { + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "requires": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "requires": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-modules-amd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", - "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", - "dev": true, + "@aws-sdk/region-config-resolver": { + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.10.tgz", + "integrity": "sha512-1dq9ToC6e070QvnVhhbAs3bb5r6cQ10gTVc6cyRV5uvQe7P138TV2uG2i6+Yok4bAkVAcx5AqkTEBUvWEtBlsQ==", "requires": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/types": "^3.973.6", + "@smithy/config-resolver": "^4.4.13", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", - "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "@aws-sdk/s3-presigned-post": { + "version": "3.1023.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-presigned-post/-/s3-presigned-post-3.1023.0.tgz", + "integrity": "sha512-cZ6WLQbIpHd1OHsXMUWR62el3uIZvDxSnpHiGFJ3kke+Pm5nzyOD02Q5yWzJWrTHJMedpzbUl4LfVfTpoyKslA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/client-s3": "3.1023.0", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-format-url": "^3.972.8", + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/signature-v4": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "dev": true, + "requires": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "dev": true, + "requires": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", - "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", - "dev": true, + "@aws-sdk/s3-request-presigner": { + "version": "3.1024.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.1024.0.tgz", + "integrity": "sha512-KNoGsXnTfBxPqrtV2Owd4mrLnhTHRsOz6Hdaz+As5czGMQuPchoRvG1BJzn2NFRGLt/HSJAXVn+G6IhtRIDe+Q==", "requires": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@aws-sdk/signature-v4-multi-region": "^3.996.15", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-format-url": "^3.972.8", + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-modules-umd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", - "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", - "dev": true, + "@aws-sdk/signature-v4-multi-region": { + "version": "3.996.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.15.tgz", + "integrity": "sha512-Ukw2RpqvaL96CjfH/FgfBmy/ZosHBqoHBCFsN61qGg99F33vpntIVii8aNeh65XuOja73arSduskoa4OJea9RQ==", "requires": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/middleware-sdk-s3": "^3.972.27", + "@aws-sdk/types": "^3.973.6", + "@smithy/protocol-http": "^5.3.12", + "@smithy/signature-v4": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", - "dev": true, + "@aws-sdk/token-providers": { + "version": "3.1021.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1021.0.tgz", + "integrity": "sha512-TKY6h9spUk3OLs5v1oAgW9mAeBE3LAGNBwJokLy96wwmd4W2v/tYlXseProyed9ValDj2u1jK/4Rg1T+1NXyJA==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/core": "^3.973.26", + "@aws-sdk/nested-clients": "^3.996.18", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-new-target": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", - "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", - "dev": true, + "@aws-sdk/types": { + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.6.tgz", + "integrity": "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==", "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.26.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", - "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", - "dev": true, + "@aws-sdk/util-arn-parser": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.3.tgz", + "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==", "requires": { - "@babel/helper-plugin-utils": "^7.26.5" + "tslib": "^2.6.2" } }, - "@babel/plugin-transform-numeric-separator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", - "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", - "dev": true, + "@aws-sdk/util-endpoints": { + "version": "3.996.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", + "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-endpoints": "^3.3.3", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-object-rest-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", - "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", - "dev": true, + "@aws-sdk/util-format-url": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.8.tgz", + "integrity": "sha512-J6DS9oocrgxM8xlUTTmQOuwRF6rnAGEujAN9SAzllcrQmwn5iJ58ogxy3SEhD0Q7JZvlA5jvIXBkpQRqEqlE9A==", "requires": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9" + "@aws-sdk/types": "^3.973.6", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", + "requires": { + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-object-super": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", - "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", - "dev": true, + "@aws-sdk/util-locate-window": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", + "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", "requires": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9" + "tslib": "^2.6.2" } }, - "@babel/plugin-transform-optional-catch-binding": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", - "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", - "dev": true, + "@aws-sdk/util-user-agent-browser": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.8.tgz", + "integrity": "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==", "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", - "dev": true, + "@aws-sdk/util-user-agent-node": { + "version": "3.973.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.14.tgz", + "integrity": "sha512-vNSB/DYaPOyujVZBg/zUznH9QC142MaTHVmaFlF7uzzfg3CgT9f/l4C0Yi+vU/tbBhxVcXVB90Oohk5+o+ZbWw==", "requires": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@aws-sdk/middleware-user-agent": "^3.972.28", + "@aws-sdk/types": "^3.973.6", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-parameters": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", - "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", - "dev": true, + "@aws-sdk/xml-builder": { + "version": "3.972.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.16.tgz", + "integrity": "sha512-iu2pyvaqmeatIJLURLqx9D+4jKAdTH20ntzB6BFwjyN7V960r4jK32mx0Zf7YbtOYAbmbtQfDNuL60ONinyw7A==", "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@smithy/types": "^4.13.1", + "fast-xml-parser": "5.5.8", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + } } }, - "@babel/plugin-transform-private-methods": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", - "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - } + "@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==" }, - "@babel/plugin-transform-private-property-in-object": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", - "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - } + "@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 + }, + "@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true }, - "@babel/plugin-transform-property-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", - "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/types": "^7.29.0" } }, - "@babel/plugin-transform-regenerator": { + "@babel/runtime": { "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.0.tgz", - "integrity": "sha512-LX/vCajUJQDqE7Aum/ELUMZAY19+cDpghxrnyt5I1tV6X5PyC86AOoWXWFYFeIvauyeSA6/ktn4tQVn/3ZifsA==", - "dev": true, + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", "requires": { - "@babel/helper-plugin-utils": "^7.26.5", - "regenerator-transform": "^0.15.2" + "regenerator-runtime": "^0.14.0" } }, - "@babel/plugin-transform-regexp-modifiers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", - "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" } }, - "@babel/plugin-transform-reserved-words": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", - "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "@biomejs/biome": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.4.tgz", + "integrity": "sha512-tigwWS5KfJf0cABVd52NVaXyAVv4qpUXOWJ1rxFL8xF1RVoeS2q/LK+FHgYoKMclJCuRoCWAPy1IXaN9/mS61Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@biomejs/cli-darwin-arm64": "2.4.4", + "@biomejs/cli-darwin-x64": "2.4.4", + "@biomejs/cli-linux-arm64": "2.4.4", + "@biomejs/cli-linux-arm64-musl": "2.4.4", + "@biomejs/cli-linux-x64": "2.4.4", + "@biomejs/cli-linux-x64-musl": "2.4.4", + "@biomejs/cli-win32-arm64": "2.4.4", + "@biomejs/cli-win32-x64": "2.4.4" } }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", - "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "@biomejs/cli-darwin-arm64": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.4.tgz", + "integrity": "sha512-jZ+Xc6qvD6tTH5jM6eKX44dcbyNqJHssfl2nnwT6vma6B1sj7ZLTGIk6N5QwVBs5xGN52r3trk5fgd3sQ9We9A==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9" - } + "optional": true }, - "@babel/plugin-transform-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", - "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "@biomejs/cli-darwin-x64": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.4.tgz", + "integrity": "sha512-Dh1a/+W+SUCXhEdL7TiX3ArPTFCQKJTI1mGncZNWfO+6suk+gYA4lNyJcBB+pwvF49uw0pEbUS49BgYOY4hzUg==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" - } + "optional": true }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", - "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "@biomejs/cli-linux-arm64": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.4.tgz", + "integrity": "sha512-V/NFfbWhsUU6w+m5WYbBenlEAz8eYnSqRMDMAW3K+3v0tYVkNyZn8VU0XPxk/lOqNXLSCCrV7FmV/u3SjCBShg==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9" - } + "optional": true }, - "@babel/plugin-transform-template-literals": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz", - "integrity": "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==", + "@biomejs/cli-linux-arm64-musl": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.4.tgz", + "integrity": "sha512-+sPAXq3bxmFwhVFJnSwkSF5Rw2ZAJMH3MF6C9IveAEOdSpgajPhoQhbbAK12SehN9j2QrHpk4J/cHsa/HqWaYQ==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.26.5" - } + "optional": true }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.0.tgz", - "integrity": "sha512-+LLkxA9rKJpNoGsbLnAgOCdESl73vwYn+V6b+5wHbrE7OGKVDPHIQvbFSzqE6rwqaCw2RE+zdJrlLkcf8YOA0w==", + "@biomejs/cli-linux-x64": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.4.tgz", + "integrity": "sha512-R4+ZCDtG9kHArasyBO+UBD6jr/FcFCTH8QkNTOCu0pRJzCWyWC4EtZa2AmUZB5h3e0jD7bRV2KvrENcf8rndBg==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.26.5" - } + "optional": true }, - "@babel/plugin-transform-typescript": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz", - "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.27.0", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-syntax-typescript": "^7.25.9" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", - "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9" - } - }, - "@babel/plugin-transform-unicode-property-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", - "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", - "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - } - }, - "@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", - "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - } - }, - "@babel/preset-env": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz", - "integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.26.8", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.26.0", - "@babel/plugin-syntax-import-attributes": "^7.26.0", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.25.9", - "@babel/plugin-transform-async-generator-functions": "^7.26.8", - "@babel/plugin-transform-async-to-generator": "^7.25.9", - "@babel/plugin-transform-block-scoped-functions": "^7.26.5", - "@babel/plugin-transform-block-scoping": "^7.25.9", - "@babel/plugin-transform-class-properties": "^7.25.9", - "@babel/plugin-transform-class-static-block": "^7.26.0", - "@babel/plugin-transform-classes": "^7.25.9", - "@babel/plugin-transform-computed-properties": "^7.25.9", - "@babel/plugin-transform-destructuring": "^7.25.9", - "@babel/plugin-transform-dotall-regex": "^7.25.9", - "@babel/plugin-transform-duplicate-keys": "^7.25.9", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-dynamic-import": "^7.25.9", - "@babel/plugin-transform-exponentiation-operator": "^7.26.3", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-for-of": "^7.26.9", - "@babel/plugin-transform-function-name": "^7.25.9", - "@babel/plugin-transform-json-strings": "^7.25.9", - "@babel/plugin-transform-literals": "^7.25.9", - "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", - "@babel/plugin-transform-member-expression-literals": "^7.25.9", - "@babel/plugin-transform-modules-amd": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.26.3", - "@babel/plugin-transform-modules-systemjs": "^7.25.9", - "@babel/plugin-transform-modules-umd": "^7.25.9", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-new-target": "^7.25.9", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", - "@babel/plugin-transform-numeric-separator": "^7.25.9", - "@babel/plugin-transform-object-rest-spread": "^7.25.9", - "@babel/plugin-transform-object-super": "^7.25.9", - "@babel/plugin-transform-optional-catch-binding": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9", - "@babel/plugin-transform-private-methods": "^7.25.9", - "@babel/plugin-transform-private-property-in-object": "^7.25.9", - "@babel/plugin-transform-property-literals": "^7.25.9", - "@babel/plugin-transform-regenerator": "^7.25.9", - "@babel/plugin-transform-regexp-modifiers": "^7.26.0", - "@babel/plugin-transform-reserved-words": "^7.25.9", - "@babel/plugin-transform-shorthand-properties": "^7.25.9", - "@babel/plugin-transform-spread": "^7.25.9", - "@babel/plugin-transform-sticky-regex": "^7.25.9", - "@babel/plugin-transform-template-literals": "^7.26.8", - "@babel/plugin-transform-typeof-symbol": "^7.26.7", - "@babel/plugin-transform-unicode-escapes": "^7.25.9", - "@babel/plugin-transform-unicode-property-regex": "^7.25.9", - "@babel/plugin-transform-unicode-regex": "^7.25.9", - "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.40.0", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } + "@biomejs/cli-linux-x64-musl": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.4.tgz", + "integrity": "sha512-gGvFTGpOIQDb5CQ2VC0n9Z2UEqlP46c4aNgHmAMytYieTGEcfqhfCFnhs6xjt0S3igE6q5GLuIXtdQt3Izok+g==", + "dev": true, + "optional": true }, - "@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "@biomejs/cli-win32-arm64": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.4.tgz", + "integrity": "sha512-trzCqM7x+Gn832zZHgr28JoYagQNX4CZkUZhMUac2YxvvyDRLJDrb5m9IA7CaZLlX6lTQmADVfLEKP1et1Ma4Q==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } + "optional": true }, - "@babel/preset-typescript": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.0.tgz", - "integrity": "sha512-vxaPFfJtHhgeOVXRKuHpHPAOgymmy8V8I65T1q53R7GCZlefKeCaTyDs3zOPHTTbmquvNlQYC5klEvWsBAtrBQ==", + "@biomejs/cli-win32-x64": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.4.tgz", + "integrity": "sha512-gnOHKVPFAAPrpoPt2t+Q6FZ7RPry/FDV3GcpU53P3PtLNnQjBmKyN2Vh/JtqXet+H4pme8CC76rScwdjDcT1/A==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.26.3", - "@babel/plugin-transform-typescript": "^7.27.0" - } + "optional": true }, - "@babel/runtime": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", - "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "@datadog/pprof": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@datadog/pprof/-/pprof-5.14.0.tgz", + "integrity": "sha512-bXYptMf0BY3NdKcAmX5aAOnozj7GFdNYE4SIdPF0/Q/Yfb2fYBIYZaN/Le6BhiDIepBvvH/H75d3EIFhB64kOw==", "requires": { - "regenerator-runtime": "^0.14.0" + "node-gyp-build": "<4.0", + "pprof-format": "^2.2.1", + "source-map": "^0.7.4" + }, + "dependencies": { + "source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==" + } } }, - "@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", "dev": true, + "optional": true, "requires": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" } }, - "@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", "dev": true, + "optional": true, "requires": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - } + "tslib": "^2.4.0" } }, - "@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, + "optional": true, "requires": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "tslib": "^2.4.0" } }, - "@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 - }, "@esbuild/aix-ppc64": { "version": "0.25.8", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", @@ -30198,97 +18961,48 @@ "dev": true, "optional": true }, - "@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^3.3.0" - } - }, - "@eslint-community/regexpp": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", - "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - } - } - }, - "@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true - }, "@fastify/accept-negotiator": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", "integrity": "sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==" }, "@fastify/accepts": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@fastify/accepts/-/accepts-5.0.2.tgz", - "integrity": "sha512-pX0OrioMz3C2cuYFOGRCNMdP3sR6daTFjeSNFrWlZVutawpPIGI5opK5h4Qa6x6C9oavdDkAjA16orneE2jAFQ==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@fastify/accepts/-/accepts-5.0.4.tgz", + "integrity": "sha512-XKtvD77ZLQ/4G5r1WhPua5+rTctt16DF4XUMBQuP8KM/Ic431GhfqjJoYvwS4aDaUhoRTiU9DGFaMZ3TRM6ctg==", "requires": { "accepts": "^1.3.8", "fastify-plugin": "^5.0.0" } }, "@fastify/ajv-compiler": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.2.tgz", - "integrity": "sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", + "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", "requires": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0" } }, + "@fastify/basic-auth": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@fastify/basic-auth/-/basic-auth-6.2.0.tgz", + "integrity": "sha512-Ao9Jf8TyW8v7p3CPy++c+E3qcCDeWfAlSIfFo0CsKrfvm81i0OCpnobIMwaSSkg/At0rzsLzbJPDWrgNru0G1w==", + "requires": { + "@fastify/error": "^4.0.0", + "fastify-plugin": "^5.0.0" + } + }, "@fastify/busboy": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.1.tgz", "integrity": "sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==" }, "@fastify/deepmerge": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-3.1.0.tgz", - "integrity": "sha512-lCVONBQINyNhM6LLezB6+2afusgEYR4G8xenMsfe+AT+iZ7Ca6upM5Ha8UkZuYSnuMw3GWl/BiPXnLMi/gSxuQ==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-3.2.1.tgz", + "integrity": "sha512-N5Oqvltoa2r9z1tbx4xjky0oRR60v+T47Ic4J1ukoVQcptLOrIdRnCSdTGmOmajZuHVKlTnfcmrjyqsGEW1ztA==" }, "@fastify/error": { "version": "4.2.0", @@ -30304,9 +19018,9 @@ } }, "@fastify/forwarded": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.0.tgz", - "integrity": "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", + "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==" }, "@fastify/merge-json-schemas": { "version": "0.2.1", @@ -30317,28 +19031,55 @@ } }, "@fastify/multipart": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-9.2.1.tgz", - "integrity": "sha512-U4221XDMfzCUtfzsyV1/PkR4MNgKI0158vUUyn/oF2Tl6RxMc+N7XYLr5fZXQiEC+Fmw5zFaTjxsTGTgtDtK+g==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-9.4.0.tgz", + "integrity": "sha512-Z404bzZeLSXTBmp/trCBuoVFX28pM7rhv849Q5TsbTFZHuk1lc4QjQITTPK92DKVpXmNtJXeHSSc7GYvqFpxAQ==", "requires": { "@fastify/busboy": "^3.0.0", "@fastify/deepmerge": "^3.0.0", "@fastify/error": "^4.0.0", "fastify-plugin": "^5.0.0", "secure-json-parse": "^4.0.0" + } + }, + "@fastify/otel": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@fastify/otel/-/otel-0.18.1.tgz", + "integrity": "sha512-7TYQXrOBRwCuTiwQm/2qCPO37af011934clxBj6F7KyF9a2a9MB+aSrWv8vMVp5N8rZC1AQ4pUp8uDs5Z40UPQ==", + "requires": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.28.0", + "minimatch": "^10.2.4" }, "dependencies": { - "secure-json-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.0.0.tgz", - "integrity": "sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==" + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" + }, + "brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "requires": { + "balanced-match": "^4.0.2" + } + }, + "minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "requires": { + "brace-expansion": "^5.0.2" + } } } }, "@fastify/proxy-addr": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.0.0.tgz", - "integrity": "sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", "requires": { "@fastify/forwarded": "^3.0.0", "ipaddr.js": "^2.1.0" @@ -30367,22 +19108,22 @@ } }, "@fastify/static": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@fastify/static/-/static-8.2.0.tgz", - "integrity": "sha512-PejC/DtT7p1yo3p+W7LiUtLMsV8fEvxAK15sozHy9t8kwo5r0uLYmhV/inURmGz1SkHZFz/8CNtHLPyhKcx4SQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-9.0.0.tgz", + "integrity": "sha512-r64H8Woe/vfilg5RTy7lwWlE8ZZcTrc3kebYFMEUBrMqlydhQyoiExQXdYAy2REVpST/G35+stAM8WYp1WGmMA==", "requires": { "@fastify/accept-negotiator": "^2.0.0", "@fastify/send": "^4.0.0", - "content-disposition": "^0.5.4", + "content-disposition": "^1.0.1", "fastify-plugin": "^5.0.0", "fastq": "^1.17.1", - "glob": "^11.0.0" + "glob": "^13.0.0" } }, "@fastify/swagger": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-9.5.1.tgz", - "integrity": "sha512-EGjYLA7vDmCPK7XViAYMF6y4+K3XUy5soVTVxsyXolNe/Svb4nFQxvtuQvvoQb2Gzc9pxiF3+ZQN/iZDHhKtTg==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-9.7.0.tgz", + "integrity": "sha512-Vp1SC1GC2Hrkd3faFILv86BzUNyFz5N4/xdExqtCgkGASOzn/x+eMe4qXIGq7cdT6wif/P/oa6r1Ruqx19paZA==", "requires": { "fastify-plugin": "^5.0.0", "json-schema-resolver": "^3.0.0", @@ -30392,1276 +19133,3620 @@ } }, "@fastify/swagger-ui": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@fastify/swagger-ui/-/swagger-ui-5.2.3.tgz", - "integrity": "sha512-e7ivEJi9EpFcxTONqICx4llbpB2jmlI+LI1NQ/mR7QGQnyDOqZybPK572zJtcdHZW4YyYTBHcP3a03f1pOh0SA==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@fastify/swagger-ui/-/swagger-ui-5.2.5.tgz", + "integrity": "sha512-ky3I0LAkXKX/prwSDpoQ3kscBKsj2Ha6Gp1/JfgQSqyx0bm9F2bE//XmGVGj2cR9l5hUjZYn60/hqn7e+OLgWQ==", "requires": { - "@fastify/static": "^8.0.0", + "@fastify/static": "^9.0.0", "fastify-plugin": "^5.0.0", "openapi-types": "^12.1.3", "rfdc": "^1.3.1", "yaml": "^2.4.1" } }, + "@fastify/websocket": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@fastify/websocket/-/websocket-11.2.0.tgz", + "integrity": "sha512-3HrDPbAG1CzUCqnslgJxppvzaAZffieOVbLp1DAy1huCSynUWPifSvfdEDUR8HlJLp3sp1A36uOM2tJogADS8w==", + "requires": { + "duplexify": "^4.1.3", + "fastify-plugin": "^5.0.0", + "ws": "^8.16.0" + } + }, "@grpc/grpc-js": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.11.2.tgz", - "integrity": "sha512-DWp92gDD7/Qkj7r8kus6/HCINeo3yPZWZ3paKgDgsbKbSpoxKg1yvN8xe2Q8uE3zOsPe3bX8FQX2+XValq2yTw==", + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", "requires": { - "@grpc/proto-loader": "^0.7.13", + "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "@grpc/proto-loader": { - "version": "0.7.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", - "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", "requires": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", - "protobufjs": "^7.2.5", + "protobufjs": "^7.5.3", "yargs": "^17.7.2" } }, - "@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" + }, + "@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + }, + "@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==" + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true + "@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==" }, - "@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true + "@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "requires": {} }, - "@ioredis/commands": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", - "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + "@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "requires": {} }, - "@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "@kubernetes/client-node": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-1.3.0.tgz", + "integrity": "sha512-IE0yrIpOT97YS5fg2QpzmPzm8Wmcdf4ueWMn+FiJSI3jgTTQT1u+LUhoYpdfhdHAVxdrNsaBg2C0UXSnOgMoCQ==", "requires": { - "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" + "@types/js-yaml": "^4.0.1", + "@types/node": "^22.0.0", + "@types/node-fetch": "^2.6.9", + "@types/stream-buffers": "^3.0.3", + "form-data": "^4.0.0", + "hpagent": "^1.2.0", + "isomorphic-ws": "^5.0.0", + "js-yaml": "^4.1.0", + "jsonpath-plus": "^10.3.0", + "node-fetch": "^2.6.9", + "openid-client": "^6.1.3", + "rfc4648": "^1.3.0", + "socks-proxy-agent": "^8.0.4", + "stream-buffers": "^3.0.2", + "tar-fs": "^3.0.8", + "ws": "^8.18.2" }, "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + "@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "requires": { + "undici-types": "~6.21.0" + } + }, + "undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + } + } + }, + "@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==" + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==" + }, + "@opentelemetry/api-logs": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.214.0.tgz", + "integrity": "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/auto-instrumentations-node": { + "version": "0.70.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.70.1.tgz", + "integrity": "sha512-r8BKs0rHtBAzZViPIuzSD2eh65fOPau0NqVsca2sACuZ6LFGu6a+QMhqq7skXz+/OqKwFr/7/b6VsaNMS+zZpQ==", + "requires": { + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/instrumentation-amqplib": "^0.59.0", + "@opentelemetry/instrumentation-aws-lambda": "^0.64.0", + "@opentelemetry/instrumentation-aws-sdk": "^0.67.0", + "@opentelemetry/instrumentation-bunyan": "^0.57.0", + "@opentelemetry/instrumentation-cassandra-driver": "^0.57.0", + "@opentelemetry/instrumentation-connect": "^0.55.0", + "@opentelemetry/instrumentation-cucumber": "^0.28.0", + "@opentelemetry/instrumentation-dataloader": "^0.29.0", + "@opentelemetry/instrumentation-dns": "^0.55.0", + "@opentelemetry/instrumentation-express": "^0.60.0", + "@opentelemetry/instrumentation-fastify": "^0.56.0", + "@opentelemetry/instrumentation-fs": "^0.31.0", + "@opentelemetry/instrumentation-generic-pool": "^0.55.0", + "@opentelemetry/instrumentation-graphql": "^0.60.0", + "@opentelemetry/instrumentation-grpc": "^0.212.0", + "@opentelemetry/instrumentation-hapi": "^0.58.0", + "@opentelemetry/instrumentation-http": "^0.212.0", + "@opentelemetry/instrumentation-ioredis": "^0.60.0", + "@opentelemetry/instrumentation-kafkajs": "^0.21.0", + "@opentelemetry/instrumentation-knex": "^0.56.0", + "@opentelemetry/instrumentation-koa": "^0.60.0", + "@opentelemetry/instrumentation-lru-memoizer": "^0.56.0", + "@opentelemetry/instrumentation-memcached": "^0.55.0", + "@opentelemetry/instrumentation-mongodb": "^0.65.0", + "@opentelemetry/instrumentation-mongoose": "^0.58.0", + "@opentelemetry/instrumentation-mysql": "^0.58.0", + "@opentelemetry/instrumentation-mysql2": "^0.58.0", + "@opentelemetry/instrumentation-nestjs-core": "^0.58.0", + "@opentelemetry/instrumentation-net": "^0.56.0", + "@opentelemetry/instrumentation-openai": "^0.10.0", + "@opentelemetry/instrumentation-oracledb": "^0.37.0", + "@opentelemetry/instrumentation-pg": "^0.64.0", + "@opentelemetry/instrumentation-pino": "^0.58.0", + "@opentelemetry/instrumentation-redis": "^0.60.0", + "@opentelemetry/instrumentation-restify": "^0.57.0", + "@opentelemetry/instrumentation-router": "^0.56.0", + "@opentelemetry/instrumentation-runtime-node": "^0.25.0", + "@opentelemetry/instrumentation-socket.io": "^0.59.0", + "@opentelemetry/instrumentation-tedious": "^0.31.0", + "@opentelemetry/instrumentation-undici": "^0.22.0", + "@opentelemetry/instrumentation-winston": "^0.56.0", + "@opentelemetry/resource-detector-alibaba-cloud": "^0.33.2", + "@opentelemetry/resource-detector-aws": "^2.12.0", + "@opentelemetry/resource-detector-azure": "^0.20.0", + "@opentelemetry/resource-detector-container": "^0.8.3", + "@opentelemetry/resource-detector-gcp": "^0.47.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/sdk-node": "^0.212.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/configuration": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.212.0.tgz", + "integrity": "sha512-D8sAY6RbqMa1W8lCeiaSL2eMCW2MF87QI3y+I6DQE1j+5GrDMwiKPLdzpa/2/+Zl9v1//74LmooCTCJBvWR8Iw==", + "requires": { + "@opentelemetry/core": "2.5.1", + "yaml": "^2.0.0" + } + }, + "@opentelemetry/context-async-hooks": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.1.tgz", + "integrity": "sha512-MHbu8XxCHcBn6RwvCt2Vpn1WnLMNECfNKYB14LI5XypcgH4IE0/DiVifVR9tAkwPMyLXN8dOoPJfya3IryLQVw==", + "requires": {} + }, + "@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.212.0.tgz", + "integrity": "sha512-/0bk6fQG+eSFZ4L6NlckGTgUous/ib5+OVdg0x4OdwYeHzV3lTEo3it1HgnPY6UKpmX7ki+hJvxjsOql8rCeZA==", + "requires": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/sdk-logs": "0.212.0" + } + }, + "@opentelemetry/exporter-logs-otlp-http": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.212.0.tgz", + "integrity": "sha512-JidJasLwG/7M9RTxV/64xotDKmFAUSBc9SNlxI32QYuUMK5rVKhHNWMPDzC7E0pCAL3cu+FyiKvsTwLi2KqPYw==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/sdk-logs": "0.212.0" + } + }, + "@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.212.0.tgz", + "integrity": "sha512-RpKB5UVfxc7c6Ta1UaCrxXDTQ0OD7BCGT66a97Q5zR1x3+9fw4dSaiqMXT/6FAWj2HyFbem6Rcu1UzPZikGTWQ==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-logs": "0.212.0", + "@opentelemetry/sdk-trace-base": "2.5.1" + } + }, + "@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.212.0.tgz", + "integrity": "sha512-/6Gqf9wpBq22XsomR1i0iPGnbQtCq2Vwnrq5oiDPjYSqveBdK1jtQbhGfmpK2mLLxk4cPDtD1ZEYdIou5K8EaA==", + "requires": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.212.0", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-metrics": "2.5.1" + } + }, + "@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.212.0.tgz", + "integrity": "sha512-8hgBw3aTTRpSTkU4b9MLf/2YVLnfWp+hfnLq/1Fa2cky+vx6HqTodo+Zv1GTIrAKMOOwgysOjufy0gTxngqeBg==", + "requires": { + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-metrics": "2.5.1" + } + }, + "@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.212.0.tgz", + "integrity": "sha512-C7I4WN+ghn3g7SnxXm2RK3/sRD0k/BYcXaK6lGU3yPjiM7a1M25MLuM6zY3PeVPPzzTZPfuS7+wgn/tHk768Xw==", + "requires": { + "@opentelemetry/core": "2.5.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.212.0", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-metrics": "2.5.1" + } + }, + "@opentelemetry/exporter-prometheus": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.212.0.tgz", + "integrity": "sha512-hJFLhCJba5MW5QHexZMHZdMhBfNqNItxOsN0AZojwD1W2kU9xM+BEICowFGJFo/vNV+I2BJvTtmuKafeDSAo7Q==", + "requires": { + "@opentelemetry/core": "2.5.1", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-metrics": "2.5.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.212.0.tgz", + "integrity": "sha512-9xTuYWp8ClBhljDGAoa0NSsJcsxJsC9zCFKMSZJp1Osb9pjXCMRdA6fwXtlubyqe7w8FH16EWtQNKx/FWi+Ghw==", + "requires": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-trace-base": "2.5.1" + } + }, + "@opentelemetry/exporter-trace-otlp-http": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.212.0.tgz", + "integrity": "sha512-v/0wMozNoiEPRolzC4YoPo4rAT0q8r7aqdnRw3Nu7IDN0CGFzNQazkfAlBJ6N5y0FYJkban7Aw5WnN73//6YlA==", + "requires": { + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-trace-base": "2.5.1" + } + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.212.0.tgz", + "integrity": "sha512-d1ivqPT0V+i0IVOOdzGaLqonjtlk5jYrW7ItutWzXL/Mk+PiYb59dymy/i2reot9dDnBFWfrsvxyqdutGF5Vig==", + "requires": { + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-trace-base": "2.5.1" + } + }, + "@opentelemetry/exporter-zipkin": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.5.1.tgz", + "integrity": "sha512-Me6JVO7WqXGXsgr4+7o+B7qwKJQbt0c8WamFnxpkR43avgG9k/niTntwCaXiXUTjonWy0+61ZuX6CGzj9nn8CQ==", + "requires": { + "@opentelemetry/core": "2.5.1", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-trace-base": "2.5.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + }, + "@opentelemetry/instrumentation-aws-sdk": { + "version": "0.67.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.67.0.tgz", + "integrity": "sha512-btpwJnZ2RBXDh/pTpfVpInpBu9Pedi+lbLKbt3naB344SggbbYnIdT7u8EzmGIApWi9EV91vw7hm896I7nESQA==", + "requires": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.34.0" + } + }, + "@opentelemetry/instrumentation-fastify": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.56.0.tgz", + "integrity": "sha512-zotOPoZsWtMF47BjottK23XaaBSmVuwG5D/R3FlGfAAwMNFoDR3IY1OGO9v9KfOU/1/xDVkxsQ22NFfu9lE8aA==", + "requires": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + } + }, + "@opentelemetry/instrumentation-http": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.212.0.tgz", + "integrity": "sha512-t2nt16Uyv9irgR+tqnX96YeToOStc3X5js7Ljn3EKlI2b4Fe76VhMkTXtsTQ0aId6AsYgefrCRnXSCo/Fn/vww==", + "requires": { + "@opentelemetry/core": "2.5.1", + "@opentelemetry/instrumentation": "0.212.0", + "@opentelemetry/semantic-conventions": "^1.29.0", + "forwarded-parse": "2.1.2" + } + }, + "@opentelemetry/instrumentation-knex": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.56.0.tgz", + "integrity": "sha512-pKqtY5lbAQ70MC5K/BJeAA1t2gAUlRBZBAJ5ergRUNs5jw8zbdOXEZOLztiuNvQqD2z4a9N0Tkde9JMFm2pKMQ==", + "requires": { + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.1" + } + }, + "@opentelemetry/otlp-exporter-base": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.212.0.tgz", + "integrity": "sha512-HoMv5pQlzbuxiMS0hN7oiUtg8RsJR5T7EhZccumIWxYfNo/f4wFc7LPDfFK6oHdG2JF/+qTocfqIHoom+7kLpw==", + "requires": { + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-transformer": "0.212.0" + } + }, + "@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.212.0.tgz", + "integrity": "sha512-YidOSlzpsun9uw0iyIWrQp6HxpMtBlECE3tiHGAsnpEqJWbAUWcMnIffvIuvTtTQ1OyRtwwaE79dWSQ8+eiB7g==", + "requires": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/otlp-exporter-base": "0.212.0", + "@opentelemetry/otlp-transformer": "0.212.0" + } }, - "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" + "@opentelemetry/otlp-transformer": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.212.0.tgz", + "integrity": "sha512-bj7zYFOg6Db7NUwsRZQ/WoVXpAf41WY2gsd3kShSfdpZQDRKHWJiRZIg7A8HvWsf97wb05rMFzPbmSHyjEl9tw==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-logs": "0.212.0", + "@opentelemetry/sdk-metrics": "2.5.1", + "@opentelemetry/sdk-trace-base": "2.5.1", + "protobufjs": "8.0.0" + } + }, + "@opentelemetry/propagator-b3": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.5.1.tgz", + "integrity": "sha512-AU6sZgunZrZv/LTeHP+9IQsSSH5p3PtOfDPe8VTdwYH69nZCfvvvXehhzu+9fMW2mgJMh5RVpiH8M9xuYOu5Dg==", + "requires": { + "@opentelemetry/core": "2.5.1" + } + }, + "@opentelemetry/propagator-jaeger": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.5.1.tgz", + "integrity": "sha512-8+SB94/aSIOVGDUPRFSBRHVUm2A8ye1vC6/qcf/D+TF4qat7PC6rbJhRxiUGDXZtMtKEPM/glgv5cBGSJQymSg==", + "requires": { + "@opentelemetry/core": "2.5.1" + } }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + "@opentelemetry/resources": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.1.tgz", + "integrity": "sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ==", + "requires": { + "@opentelemetry/core": "2.5.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + } }, - "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "@opentelemetry/sdk-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.212.0.tgz", + "integrity": "sha512-qglb5cqTf0mOC1sDdZ7nfrPjgmAqs2OxkzOPIf2+Rqx8yKBK0pS7wRtB1xH30rqahBIut9QJDbDePyvtyqvH/Q==", "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "@opentelemetry/api-logs": "0.212.0", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/resources": "2.5.1" } }, - "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "@opentelemetry/sdk-metrics": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.1.tgz", + "integrity": "sha512-RKMn3QKi8nE71ULUo0g/MBvq1N4icEBo7cQSKnL3URZT16/YH3nSVgWegOjwx7FRBTrjOIkMJkCUn/ZFIEfn4A==", "requires": { - "ansi-regex": "^6.0.1" + "@opentelemetry/core": "2.5.1", + "@opentelemetry/resources": "2.5.1" } }, - "wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "@opentelemetry/sdk-node": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.212.0.tgz", + "integrity": "sha512-tJzVDk4Lo44MdgJLlP+gdYdMnjxSNsjC/IiTxj5CFSnsjzpHXwifgl3BpUX67Ty3KcdubNVfedeBc/TlqHXwwg==", "requires": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@opentelemetry/api-logs": "0.212.0", + "@opentelemetry/configuration": "0.212.0", + "@opentelemetry/context-async-hooks": "2.5.1", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/exporter-logs-otlp-grpc": "0.212.0", + "@opentelemetry/exporter-logs-otlp-http": "0.212.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.212.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.212.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.212.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.212.0", + "@opentelemetry/exporter-prometheus": "0.212.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.212.0", + "@opentelemetry/exporter-trace-otlp-http": "0.212.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.212.0", + "@opentelemetry/exporter-zipkin": "2.5.1", + "@opentelemetry/instrumentation": "0.212.0", + "@opentelemetry/propagator-b3": "2.5.1", + "@opentelemetry/propagator-jaeger": "2.5.1", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/sdk-logs": "0.212.0", + "@opentelemetry/sdk-metrics": "2.5.1", + "@opentelemetry/sdk-trace-base": "2.5.1", + "@opentelemetry/sdk-trace-node": "2.5.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.1.tgz", + "integrity": "sha512-iZH3Gw8cxQn0gjpOjJMmKLd9GIaNh/E3v3ST67vyzLSxHBs14HsG4dy7jMYyC5WXGdBVEcM7U/XTF5hCQxjDMw==", + "requires": { + "@opentelemetry/core": "2.5.1", + "@opentelemetry/resources": "2.5.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/sdk-trace-node": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.5.1.tgz", + "integrity": "sha512-9lopQ6ZoElETOEN0csgmtEV5/9C7BMfA7VtF4Jape3i954b6sTY2k3Xw3CxUTKreDck/vpAuJM+EDo4zheUw+A==", + "requires": { + "@opentelemetry/context-async-hooks": "2.5.1", + "@opentelemetry/core": "2.5.1", + "@opentelemetry/sdk-trace-base": "2.5.1" + } + }, + "protobufjs": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.0.0.tgz", + "integrity": "sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" } } } }, - "@isaacs/ttlcache": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", - "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==" - }, - "@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, + "@opentelemetry/configuration": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.213.0.tgz", + "integrity": "sha512-MfVgZiUuwL1d3bPPvXcEkVHGTGNUGoqGK97lfwBuRoKttcVGGqDyxTCCVa5MGbirtBQkUTysXMBUVWPaq7zbWw==", "requires": { - "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" + "@opentelemetry/core": "2.6.0", + "yaml": "^2.0.0" }, "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "@opentelemetry/semantic-conventions": "^1.29.0" } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true } } }, - "@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 + "@opentelemetry/context-async-hooks": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.6.0.tgz", + "integrity": "sha512-L8UyDwqpTcbkIK5cgwDRDYDoEhQoj8wp8BwsO19w3LB1Z41yEQm2VJyNfAi9DrLP/YTqXqWpKHyZfR9/tFYo1Q==", + "requires": {} }, - "@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, + "@opentelemetry/core": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.1.tgz", + "integrity": "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA==", "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" + "@opentelemetry/semantic-conventions": "^1.29.0" } }, - "@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, + "@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.213.0.tgz", + "integrity": "sha512-QiRZzvayEOFnenSXi85Eorgy5WTqyNQ+E7gjl6P6r+W3IUIwAIH8A9/BgMWfP056LwmdrBL6+qvnwaIEmug6Yg==", "requires": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/sdk-logs": "0.213.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } } }, - "@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, + "@opentelemetry/exporter-logs-otlp-http": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.213.0.tgz", + "integrity": "sha512-vqDVSpLp09ZzcFIdb7QZrEFPxUlO3GzdhBKLstq3jhYB5ow3+ZtV5V0ngSdi/0BZs+J5WPiN1+UDV4X5zD/GzA==", "requires": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" + "@opentelemetry/api-logs": "0.213.0", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/sdk-logs": "0.213.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.213.0.tgz", + "integrity": "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } } }, - "@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, + "@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.213.0.tgz", + "integrity": "sha512-gQk41nqfK3KhDk8jbSo3LR/fQBlV7f6Q5xRcfDmL1hZlbgXQPdVFV9/rIfYUrCoq1OM+2NnKnFfGjBt6QpLSsA==", "requires": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "@opentelemetry/api-logs": "0.213.0", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-logs": "0.213.0", + "@opentelemetry/sdk-trace-base": "2.6.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.213.0.tgz", + "integrity": "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } } }, - "@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, + "@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.215.0.tgz", + "integrity": "sha512-1TAMliHQvzc+v1OtnLMHSk5sU8BSkJbxIKrWzuCWcQjajWrvem/r5ugLK6agI0PjPz/ADfZju5AVYedlNyeO9g==", "requires": { - "jest-get-type": "^29.6.3" + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.7.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.215.0", + "@opentelemetry/otlp-exporter-base": "0.215.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.215.0", + "@opentelemetry/otlp-transformer": "0.215.0", + "@opentelemetry/resources": "2.7.0", + "@opentelemetry/sdk-metrics": "2.7.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.215.0.tgz", + "integrity": "sha512-xrFlqhdhUyO8wSRn6DjE0145/HPWSJ5Nm0C7vWua6TdL/FSEAZvEyvdsa9CRXuxo9ebb7j/NEPhEcO62IJ0qUA==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/core": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.0.tgz", + "integrity": "sha512-DT12SXVwV2eoJrGf4nnsvZojxxeQo+LlNAsoYGRRObPWTeN6APiqZ2+nqDCQDvQX40eLi1AePONS0onoASp3yQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.215.0.tgz", + "integrity": "sha512-FRydO5j7MWnXK9ghfykKxiSM8I5UeiicK/UNl3/mv86xoEKkb+LKz1I3WXgkuYVOQf22VNqbPO58s2W1mVWtEQ==", + "requires": { + "@opentelemetry/core": "2.7.0", + "@opentelemetry/otlp-exporter-base": "0.215.0", + "@opentelemetry/otlp-transformer": "0.215.0", + "@opentelemetry/resources": "2.7.0", + "@opentelemetry/sdk-metrics": "2.7.0" + } + }, + "@opentelemetry/otlp-exporter-base": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.215.0.tgz", + "integrity": "sha512-lHrfbmeLSmesGSkkHiqDwOzfaEMSWXdc7q6UoLfbW8byONCb+bE/zkAr0kapN4US1baT/2nbpNT7Cn9XoB96Vg==", + "requires": { + "@opentelemetry/core": "2.7.0", + "@opentelemetry/otlp-transformer": "0.215.0" + } + }, + "@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.215.0.tgz", + "integrity": "sha512-WkuHkUrhwNxTKrm7Xuf6S+HmLNbk2T8S2YiZhN606RfgetSQb9xLp4NizWLwXvw63uxGsBaK262dirFO2yht2g==", + "requires": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.7.0", + "@opentelemetry/otlp-exporter-base": "0.215.0", + "@opentelemetry/otlp-transformer": "0.215.0" + } + }, + "@opentelemetry/otlp-transformer": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.215.0.tgz", + "integrity": "sha512-cWwBvaV+vkXHkSoTYR8hGw+AW03UlgTr6xtrUKOMeum3T+8vffYXIfXu6KY5MLu8O9QtoBKqaKWw9I5xoOepng==", + "requires": { + "@opentelemetry/api-logs": "0.215.0", + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0", + "@opentelemetry/sdk-logs": "0.215.0", + "@opentelemetry/sdk-metrics": "2.7.0", + "@opentelemetry/sdk-trace-base": "2.7.0", + "protobufjs": "^8.0.1" + } + }, + "@opentelemetry/resources": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.0.tgz", + "integrity": "sha512-K+oi0hNMv94EpZbnW3eyu2X6SGVpD3O5DhG2NIp65Hc7lhAj9brRXTAVzh3wB82+q3ThakEf7Zd7RsFUqcTc7A==", + "requires": { + "@opentelemetry/core": "2.7.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/sdk-logs": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.215.0.tgz", + "integrity": "sha512-y3ucOmphzc4vgBTyIGchs+N/1rkACmoka8QalT2z1LBNM232Z17zMYayHcMl+dgMoOadZ0b72UZv7mDtqy1cFA==", + "requires": { + "@opentelemetry/api-logs": "0.215.0", + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.0.tgz", + "integrity": "sha512-Yg9zEXJB50DLVLpsKPk7NmNqlPlS+OvqhJGh0A8oawIOTPOwlm4eXs9BMJV7L79lvEwI+dWtAj+YjTyddV336A==", + "requires": { + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "protobufjs": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.0.1.tgz", + "integrity": "sha512-NWWCCscLjs+cOKF/s/XVNFRW7Yih0fdH+9brffR5NZCy8k42yRdl5KlWKMVXuI1vfCoy4o1z80XR/W/QUb3V3w==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + } + } } }, - "@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, + "@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.213.0.tgz", + "integrity": "sha512-yw3fTIw4KQIRXC/ZyYQq5gtA3Ogfdfz/g5HVgleobQAcjUUE8Nj3spGMx8iQPp+S+u6/js7BixufRkXhzLmpJA==", "requires": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-metrics": "2.6.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/sdk-metrics": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.0.tgz", + "integrity": "sha512-CicxWZxX6z35HR83jl+PLgtFgUrKRQ9LCXyxgenMnz5A1lgYWfAog7VtdOvGkJYyQgMNPhXQwkYrDLujk7z1Iw==", + "requires": { + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0" + } + } } }, - "@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, + "@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.213.0.tgz", + "integrity": "sha512-geHF+zZaDb0/WRkJTxR8o8dG4fCWT/Wq7HBdNZCxwH5mxhwRi/5f37IDYH7nvU+dwU6IeY4Pg8TPI435JCiNkg==", "requires": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.213.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-metrics": "2.6.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/sdk-metrics": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.0.tgz", + "integrity": "sha512-CicxWZxX6z35HR83jl+PLgtFgUrKRQ9LCXyxgenMnz5A1lgYWfAog7VtdOvGkJYyQgMNPhXQwkYrDLujk7z1Iw==", + "requires": { + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0" + } + } } }, - "@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, + "@opentelemetry/exporter-prometheus": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.213.0.tgz", + "integrity": "sha512-FyV3/JfKGAgx+zJUwCHdjQHbs+YeGd2fOWvBHYrW6dmfv/w89lb8WhJTSZEoWgP525jwv/gFeBttlGu1flebdA==", "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-metrics": "2.6.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "@opentelemetry/semantic-conventions": "^1.29.0" } }, - "istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, + "@opentelemetry/sdk-metrics": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.0.tgz", + "integrity": "sha512-CicxWZxX6z35HR83jl+PLgtFgUrKRQ9LCXyxgenMnz5A1lgYWfAog7VtdOvGkJYyQgMNPhXQwkYrDLujk7z1Iw==", "requires": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0" } } } }, - "@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, + "@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.213.0.tgz", + "integrity": "sha512-L8y6piP4jBIIx1Nv7/9hkx25ql6/Cro/kQrs+f9e8bPF0Ar5Dm991v7PnbtubKz6Q4fT872H56QXUWVnz/Cs4Q==", "requires": { - "@sinclair/typebox": "^0.27.8" + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-trace-base": "2.6.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } } }, - "@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, + "@opentelemetry/exporter-trace-otlp-http": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.213.0.tgz", + "integrity": "sha512-tnRmJD39aWrE/Sp7F6AbRNAjKHToDkAqBi6i0lESpGWz3G+f4bhVAV6mgSXH2o18lrDVJXo6jf9bAywQw43wRA==", "requires": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-trace-base": "2.6.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } } }, - "@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, + "@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.213.0.tgz", + "integrity": "sha512-six3vPq3sL+ge1iZOfKEg+RHuFQhGb8ZTdlvD234w/0gi8ty/qKD46qoGpKvM3amy5yYunWBKiFBW47WaVS26w==", "requires": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-trace-base": "2.6.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } } }, - "@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, + "@opentelemetry/exporter-zipkin": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.6.0.tgz", + "integrity": "sha512-AFP77OQMLfw/Jzh6WT2PtrywstNjdoyT9t9lYrYdk1s4igsvnMZ8DkZKCwxsItC01D+4Lydgrb+Wy0bAvpp8xg==", "requires": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-trace-base": "2.6.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } } }, - "@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, + "@opentelemetry/host-metrics": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@opentelemetry/host-metrics/-/host-metrics-0.38.3.tgz", + "integrity": "sha512-8iSOA8VPGoB5p/RIC8n/dcSe4cluCEWoznWENZfXR8sWQOQvergFu7v798xp7S5WQlZo1zfn1nVXx8dbyQ9m6Q==", "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - } - }, - "@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "systeminformation": "^5.31.1" } }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, + "@opentelemetry/instrumentation": { + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.214.0.tgz", + "integrity": "sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w==", "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@opentelemetry/api-logs": "0.214.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" + }, + "dependencies": { + "cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==" + }, + "import-in-the-middle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.0.tgz", + "integrity": "sha512-OnGy+eYT7wVejH2XWgLRgbmzujhhVIATQH0ztIeRilwHBjTeG3pD+XnH3PKX0r9gJ0BuJmJ68q/oh9qgXnNDQg==", + "requires": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + } + } } }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, + "@opentelemetry/instrumentation-amqplib": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.59.0.tgz", + "integrity": "sha512-xscSgOJA+GHphESDZxBHNk/zjNaEgoeufMwmiqYdL+qM27Xw3BbR9vN6Ucbq9dW6Y+oYUPgTTj17qf+Za4+uzg==", "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==" - }, - "@lukeed/ms": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", - "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==" - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, + "@opentelemetry/instrumentation-aws-lambda": { + "version": "0.64.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.64.0.tgz", + "integrity": "sha512-vYhM/a8fG34/Dl/Q9gfv5Ih3OFPgqeyn79S8FN+Xs/QZw6h6L8a1lDa3CyigyicOXLCmVIM7Fc9vFD4BGqgGLA==", "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/aws-lambda": "^8.10.155" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, + "@opentelemetry/instrumentation-aws-sdk": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.59.0.tgz", + "integrity": "sha512-GN/9YGBMb//s0vnchM2jMCkCaIKDB/Piau72fcuqcDXNBffMgu+AA9vCHZD2umriciXLtXJ2GXTh2/yaaHwLIw==", "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.204.0", + "@opentelemetry/semantic-conventions": "^1.34.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.204.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.204.0.tgz", + "integrity": "sha512-DqxY8yoAaiBPivoJD4UtgrMS8gEmzZ5lnaxzPojzLVHBGqPxgWm4zcuvcUHZiqQ6kRX2Klel2r9y8cA2HAtqpw==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.204.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.204.0.tgz", + "integrity": "sha512-vV5+WSxktzoMP8JoYWKeopChy6G3HKk4UQ2hESCRDUUTZqQ3+nM3u8noVG0LmNfRWwcFBnbZ71GKC7vaYYdJ1g==", + "requires": { + "@opentelemetry/api-logs": "0.204.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1" + } + }, + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "requires": { + "ms": "^2.1.3" + } + }, + "import-in-the-middle": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz", + "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==", + "requires": { + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "require-in-the-middle": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", + "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", + "requires": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" + } + } } }, - "@opentelemetry/api": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz", - "integrity": "sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==" - }, - "@opentelemetry/api-logs": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", - "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "@opentelemetry/instrumentation-bunyan": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.57.0.tgz", + "integrity": "sha512-W4zLz1Y9ptCsdL+QMXR7xQaBHkJivLBmVlLCjUe23rX4V8E65fGAtlIJSKTKAfz4aEgtWgQAGMdkeqACwG0Caw==", "requires": { - "@opentelemetry/api": "^1.0.0" + "@opentelemetry/api-logs": "^0.212.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@types/bunyan": "1.8.11" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/auto-instrumentations-node": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.50.0.tgz", - "integrity": "sha512-LqoSiWrOM4Cnr395frDHL4R/o5c2fuqqrqW8sZwhxvkasImmVlyL66YMPHllM2O5xVj2nP2ANUKHZSd293meZA==", - "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/instrumentation-amqplib": "^0.42.0", - "@opentelemetry/instrumentation-aws-lambda": "^0.44.0", - "@opentelemetry/instrumentation-aws-sdk": "^0.44.0", - "@opentelemetry/instrumentation-bunyan": "^0.41.0", - "@opentelemetry/instrumentation-cassandra-driver": "^0.41.0", - "@opentelemetry/instrumentation-connect": "^0.39.0", - "@opentelemetry/instrumentation-cucumber": "^0.9.0", - "@opentelemetry/instrumentation-dataloader": "^0.12.0", - "@opentelemetry/instrumentation-dns": "^0.39.0", - "@opentelemetry/instrumentation-express": "^0.42.0", - "@opentelemetry/instrumentation-fastify": "^0.39.0", - "@opentelemetry/instrumentation-fs": "^0.15.0", - "@opentelemetry/instrumentation-generic-pool": "^0.39.0", - "@opentelemetry/instrumentation-graphql": "^0.43.0", - "@opentelemetry/instrumentation-grpc": "^0.53.0", - "@opentelemetry/instrumentation-hapi": "^0.41.0", - "@opentelemetry/instrumentation-http": "^0.53.0", - "@opentelemetry/instrumentation-ioredis": "^0.43.0", - "@opentelemetry/instrumentation-kafkajs": "^0.3.0", - "@opentelemetry/instrumentation-knex": "^0.40.0", - "@opentelemetry/instrumentation-koa": "^0.43.0", - "@opentelemetry/instrumentation-lru-memoizer": "^0.40.0", - "@opentelemetry/instrumentation-memcached": "^0.39.0", - "@opentelemetry/instrumentation-mongodb": "^0.47.0", - "@opentelemetry/instrumentation-mongoose": "^0.42.0", - "@opentelemetry/instrumentation-mysql": "^0.41.0", - "@opentelemetry/instrumentation-mysql2": "^0.41.0", - "@opentelemetry/instrumentation-nestjs-core": "^0.40.0", - "@opentelemetry/instrumentation-net": "^0.39.0", - "@opentelemetry/instrumentation-pg": "^0.44.0", - "@opentelemetry/instrumentation-pino": "^0.42.0", - "@opentelemetry/instrumentation-redis": "^0.42.0", - "@opentelemetry/instrumentation-redis-4": "^0.42.0", - "@opentelemetry/instrumentation-restify": "^0.41.0", - "@opentelemetry/instrumentation-router": "^0.40.0", - "@opentelemetry/instrumentation-socket.io": "^0.42.0", - "@opentelemetry/instrumentation-tedious": "^0.14.0", - "@opentelemetry/instrumentation-undici": "^0.6.0", - "@opentelemetry/instrumentation-winston": "^0.40.0", - "@opentelemetry/resource-detector-alibaba-cloud": "^0.29.1", - "@opentelemetry/resource-detector-aws": "^1.6.1", - "@opentelemetry/resource-detector-azure": "^0.2.11", - "@opentelemetry/resource-detector-container": "^0.4.1", - "@opentelemetry/resource-detector-gcp": "^0.29.11", - "@opentelemetry/resources": "^1.24.0", - "@opentelemetry/sdk-node": "^0.53.0" + "@opentelemetry/instrumentation-cassandra-driver": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.57.0.tgz", + "integrity": "sha512-xLwrK+XnN32IB5i6t/a2j+SVdjlq/BIgjpVRkke4HAsKjoSMy1GeSI+ZOiJffRLFb4MojcvH4RG2+nEg1uC6Zg==", + "requires": { + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.37.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/context-async-hooks": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.26.0.tgz", - "integrity": "sha512-HedpXXYzzbaoutw6DFLWLDket2FwLkLpil4hGCZ1xYEIMTcivdfwEOISgdbLEWyG3HW52gTq2V9mOVJrONgiwg==", - "requires": {} - }, - "@opentelemetry/core": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.26.0.tgz", - "integrity": "sha512-1iKxXXE8415Cdv0yjG3G6hQnB5eVEsJce3QaawX8SjDn0mAS0ZM8fAbZZJD4ajvhC15cePvosSCut404KrIIvQ==", + "@opentelemetry/instrumentation-connect": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.55.0.tgz", + "integrity": "sha512-UfGw7ubKKZBoTRjxi5KlfeECEaXZinS20RdRNlZE5tVF+O17hJOnrcGwAoQAHp6eYmxI2jW9IQ4t6450gnNF9g==", "requires": { - "@opentelemetry/semantic-conventions": "1.27.0" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/connect": "3.4.38" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/exporter-logs-otlp-grpc": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.53.0.tgz", - "integrity": "sha512-x5ygAQgWAQOI+UOhyV3z9eW7QU2dCfnfOuIBiyYmC2AWr74f6x/3JBnP27IAcEx6aihpqBYWKnpoUTztkVPAZw==", + "@opentelemetry/instrumentation-cucumber": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.28.0.tgz", + "integrity": "sha512-kim+bRxu4LZqKEyF2SgO01tgG88W+/iYltyP1XjT31FIXzlBjzQpwtSLLM8byayO85mcZIBha54WSNFDLM/7qQ==", "requires": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/sdk-logs": "0.53.0" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/exporter-logs-otlp-http": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.53.0.tgz", - "integrity": "sha512-cSRKgD/n8rb+Yd+Cif6EnHEL/VZg1o8lEcEwFji1lwene6BdH51Zh3feAD9p2TyVoBKrl6Q9Zm2WltSp2k9gWQ==", + "@opentelemetry/instrumentation-dataloader": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.29.0.tgz", + "integrity": "sha512-220WjRb1G1UiAKbVblSMxwxxFdpyB4wj1XYIO9BJs5r62Azj2dL5fyZiXK3/WO6wB3uLul9R946iKI1bpPxktQ==", "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/sdk-logs": "0.53.0" + "@opentelemetry/instrumentation": "^0.212.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/exporter-logs-otlp-proto": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.53.0.tgz", - "integrity": "sha512-jhEcVL1deeWNmTUP05UZMriZPSWUBcfg94ng7JuBb1q2NExgnADQFl1VQQ+xo62/JepK+MxQe4xAwlsDQFbISA==", + "@opentelemetry/instrumentation-dns": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.55.0.tgz", + "integrity": "sha512-cfWLaFi22V+sQrKY7t6QroYzT3kO9m3PpkN1OXYmuCyfwxQaXOVlF8NSAHtua/RQYw0aQl+2fe6JOWyJdEZiwA==", "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-trace-base": "1.26.0" + "@opentelemetry/instrumentation": "^0.212.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/exporter-trace-otlp-grpc": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.53.0.tgz", - "integrity": "sha512-m6KSh6OBDwfDjpzPVbuJbMgMbkoZfpxYH2r262KckgX9cMYvooWXEKzlJYsNDC6ADr28A1rtRoUVRwNfIN4tUg==", + "@opentelemetry/instrumentation-express": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.60.0.tgz", + "integrity": "sha512-KghHCDqKq0D7iuPIVCuPSXut5WVAI6uwKcPrhwTUJL5VE2LC18id2vKoiAm1V8XvVlgIGAiECtEvbrFwkTCj3g==", "requires": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/exporter-trace-otlp-http": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.53.0.tgz", - "integrity": "sha512-m7F5ZTq+V9mKGWYpX8EnZ7NjoqAU7VemQ1E2HAG+W/u0wpY1x0OmbxAXfGKFHCspdJk8UKlwPGrpcB8nay3P8A==", + "@opentelemetry/instrumentation-fs": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.31.0.tgz", + "integrity": "sha512-C7tdXGDnkMgLVlE79VSekB+Y+P345zKUigvFMs5M7U0GIYA8ERx3FS0aAcY/ICIq9YwRmH2uuMb++Br5M2vNUg==", "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/exporter-trace-otlp-proto": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.53.0.tgz", - "integrity": "sha512-T/bdXslwRKj23S96qbvGtaYOdfyew3TjPEKOk5mHjkCmkVl1O9C/YMdejwSsdLdOq2YW30KjR9kVi0YMxZushQ==", + "@opentelemetry/instrumentation-generic-pool": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.55.0.tgz", + "integrity": "sha512-7hWiyLbEX/dIS4LZy/h8VaAQPs8oBeEqsrysDWbos0b9PF414L6Rsbi2um/omtxIs+GTvsbuqDscWigeaxyWdA==", "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0" + "@opentelemetry/instrumentation": "^0.212.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/exporter-zipkin": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.26.0.tgz", - "integrity": "sha512-PW5R34n3SJHO4t0UetyHKiXL6LixIqWN6lWncg3eRXhKuT30x+b7m5sDJS0kEWRfHeS+kG7uCw2vBzmB2lk3Dw==", + "@opentelemetry/instrumentation-graphql": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.60.0.tgz", + "integrity": "sha512-XPATrmxAd2tFCsYbJ3eVIXt+gyvMKjc36QQuQxjtssMnAbw006Le9b5lKs7WXik7ItOpM1exATi1aDdOcCjRRg==", "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" + "@opentelemetry/instrumentation": "^0.212.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", - "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "@opentelemetry/instrumentation-grpc": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.212.0.tgz", + "integrity": "sha512-r1t7LNKWVhSQMUrBdDJtooFmmLZ93kGuFixqeXPoUP8W+chJCxhey9l0c0+L3xriNdyB7TzvkKHhPXUDevgVEA==", "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@types/shimmer": "^1.2.0", - "import-in-the-middle": "^1.8.1", - "require-in-the-middle": "^7.1.1", - "semver": "^7.5.2", - "shimmer": "^1.2.1" + "@opentelemetry/instrumentation": "0.212.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-amqplib": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.42.0.tgz", - "integrity": "sha512-fiuU6OKsqHJiydHWgTRQ7MnIrJ2lEqsdgFtNIH4LbAUJl/5XmrIeoDzDnox+hfkgWK65jsleFuQDtYb5hW1koQ==", + "@opentelemetry/instrumentation-hapi": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.58.0.tgz", + "integrity": "sha512-reuRApR2KHm2VsfyDgsrLhNE+IOy4uIU6n3oMjUleReHacEEZmf4vXxdt4/qcmJ6GoUXnRN2AOu3s5N3pMrgYA==", "requires": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-aws-lambda": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.44.0.tgz", - "integrity": "sha512-6vmr7FJIuopZzsQjEQTp4xWtF6kBp7DhD7pPIN8FN0dKUKyuVraABIpgWjMfelaUPy4rTYUGkYqPluhG0wx8Dw==", + "@opentelemetry/instrumentation-http": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.213.0.tgz", + "integrity": "sha512-B978Xsm5XEPGhm1P07grDoaOFLHapJPkOG9h016cJsyWWxmiLnPu2M/4Nrm7UCkHSiLnkXgC+zVGUAIahy8EEA==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/propagator-aws-xray": "^1.3.1", - "@opentelemetry/resources": "^1.8.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/aws-lambda": "8.10.143" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/instrumentation": "0.213.0", + "@opentelemetry/semantic-conventions": "^1.29.0", + "forwarded-parse": "2.1.2" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.213.0.tgz", + "integrity": "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.213.0.tgz", + "integrity": "sha512-3i9NdkET/KvQomeh7UaR/F4r9P25Rx6ooALlWXPIjypcEOUxksCmVu0zA70NBJWlrMW1rPr/LRidFAflLI+s/w==", + "requires": { + "@opentelemetry/api-logs": "0.213.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" + } + }, + "cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==" + }, + "import-in-the-middle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.0.tgz", + "integrity": "sha512-OnGy+eYT7wVejH2XWgLRgbmzujhhVIATQH0ztIeRilwHBjTeG3pD+XnH3PKX0r9gJ0BuJmJ68q/oh9qgXnNDQg==", + "requires": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + } + } } }, - "@opentelemetry/instrumentation-aws-sdk": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.44.0.tgz", - "integrity": "sha512-HIWFg4TDQsayceiikOnruMmyQ0SZYW6WiR+wknWwWVLHC3lHTCpAnqzp5V42ckArOdlwHZu2Jvq2GMSM4Myx3w==", + "@opentelemetry/instrumentation-ioredis": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.60.0.tgz", + "integrity": "sha512-R+nnbPD9l2ruzu248qM3YDWzpdmWVaFFFv08lQqsc0EP4pT/B1GGUg06/tHOSo3L5njB2eejwyzpkvJkjaQEMA==", "requires": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/propagation-utils": "^0.30.11", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/redis-common": "^0.38.2", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-bunyan": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-bunyan/-/instrumentation-bunyan-0.41.0.tgz", - "integrity": "sha512-NoQS+gcwQ7pzb2PZFyra6bAxDAVXBMmpKxBblEuXJWirGrAksQllg9XTdmqhrwT/KxUYrbVca/lMams7e51ysg==", + "@opentelemetry/instrumentation-kafkajs": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.21.0.tgz", + "integrity": "sha512-lkLrILnKGO7SHw1xPJnuGx2S4XwbKmQiJyzUGuEImRoU/6Gj0Nka0lkbeRd4ANN20dxr/mLdXIsUsk6DzTrX6A==", "requires": { - "@opentelemetry/api-logs": "^0.53.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@types/bunyan": "1.8.9" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.30.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-cassandra-driver": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cassandra-driver/-/instrumentation-cassandra-driver-0.41.0.tgz", - "integrity": "sha512-hvTNcC8qjCQEHZTLAlTmDptjsEGqCKpN+90hHH8Nn/GwilGr5TMSwGrlfstdJuZWyw8HAnRUed6bcjvmHHk2Xw==", + "@opentelemetry/instrumentation-knex": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.58.0.tgz", + "integrity": "sha512-Hc/o8fSsaWxZ8r1Yw4rNDLwTpUopTf4X32y4W6UhlHmW8Wizz8wfhgOKIelSeqFVTKBBPIDUOsQWuIMxBmu8Bw==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/instrumentation": "^0.214.0", + "@opentelemetry/semantic-conventions": "^1.33.1" } }, - "@opentelemetry/instrumentation-connect": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.39.0.tgz", - "integrity": "sha512-pGBiKevLq7NNglMgqzmeKczF4XQMTOUOTkK8afRHMZMnrK3fcETyTH7lVaSozwiOM3Ws+SuEmXZT7DYrrhxGlg==", + "@opentelemetry/instrumentation-koa": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.60.0.tgz", + "integrity": "sha512-UOmu2y2LHgPzKsm9xd0sCQJimr11YP4MKFc190Do1ufd8qds7Zd5BI3f6TudqYhH9dUIhojsQyUaS6K4nv5FsQ==", "requires": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/connect": "3.4.36" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.36.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-cucumber": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-cucumber/-/instrumentation-cucumber-0.9.0.tgz", - "integrity": "sha512-4PQNFnIqnA2WM3ZHpr0xhZpHSqJ5xJ6ppTIzZC7wPqe+ZBpj41vG8B6ieqiPfq+im4QdqbYnzLb3rj48GDEN9g==", + "@opentelemetry/instrumentation-lru-memoizer": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.56.0.tgz", + "integrity": "sha512-vXtOValhKRgWA9tLAiTU3P37Q31OveRuM2N5iLSVHl4GzkMBQ5p50A9kSKvt5gReL6BzFDXPCM9ItJiAhSS2KQ==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/instrumentation": "^0.212.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-dataloader": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.12.0.tgz", - "integrity": "sha512-pnPxatoFE0OXIZDQhL2okF//dmbiWFzcSc8pUg9TqofCLYZySSxDCgQc69CJBo5JnI3Gz1KP+mOjS4WAeRIH4g==", + "@opentelemetry/instrumentation-memcached": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.55.0.tgz", + "integrity": "sha512-kdhW/j5X+vNCAvHVc50PZfvE7diUScg1ZkBaNFRygY3Z6IUjgPLR0luWQMDPSFun6AVo1HaMDPxbUqJrot6qrA==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/memcached": "^2.2.6" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-dns": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dns/-/instrumentation-dns-0.39.0.tgz", - "integrity": "sha512-+iPzvXqVdJa67QBuz2tuP0UI3LS1/cMMo6dS7360DDtOQX+sQzkiN+mo3Omn4T6ZRhkTDw6c7uwsHBcmL31+1g==", + "@opentelemetry/instrumentation-mongodb": { + "version": "0.65.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.65.0.tgz", + "integrity": "sha512-hOAJRs5vrY7fZolSYUXmf29Y+HFDHWrek0DeLq82uwMPjPSda7h6oumQnqEX5olzw357q/QG39/uJdkclJ/JUg==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "semver": "^7.5.4" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-express": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.42.0.tgz", - "integrity": "sha512-YNcy7ZfGnLsVEqGXQPT+S0G1AE46N21ORY7i7yUQyfhGAL4RBjnZUqefMI0NwqIl6nGbr1IpF0rZGoN8Q7x12Q==", + "@opentelemetry/instrumentation-mongoose": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.58.0.tgz", + "integrity": "sha512-3L0Fqo1y2oreISFPWaqdt/bg3NhLgrkn5U/E/9RNG1QaM81drTMBCHseMY1q8SlejjE43ZWOy+0KbmRBlUPJ+g==", "requires": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-fastify": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.39.0.tgz", - "integrity": "sha512-SS9uSlKcsWZabhBp2szErkeuuBDgxOUlllwkS92dVaWRnMmwysPhcEgHKB8rUe3BHg/GnZC1eo1hbTZv4YhfoA==", + "@opentelemetry/instrumentation-mysql": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.58.0.tgz", + "integrity": "sha512-wZDrBCL3WfJclV6KywWVV3/B2ZiUYmDQdgyu3pq4jK/5qSfoDmezHzT/Nayln5MVVWMAGXIMLrCj8BKa6jaKQQ==", "requires": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/mysql": "2.15.27" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-fs": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.15.0.tgz", - "integrity": "sha512-JWVKdNLpu1skqZQA//jKOcKdJC66TWKqa2FUFq70rKohvaSq47pmXlnabNO+B/BvLfmidfiaN35XakT5RyMl2Q==", + "@opentelemetry/instrumentation-mysql2": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.58.0.tgz", + "integrity": "sha512-EubjV1XZb7XHrENqF7TW2lnah+KN0LddMneKNAB8PjGVKL5lJkVV/vhJ6EIcUNn9nCWmAwZ3GRcFVEDKCnyXfQ==", + "requires": { + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@opentelemetry/sql-common": "^0.41.2" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } + } + }, + "@opentelemetry/instrumentation-nestjs-core": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.58.0.tgz", + "integrity": "sha512-0lE9oW8j6nmvBHJoOxIQgKzMQQYNfX1nhiWZdXD0sNAMFsWBtvECWS7NAPSroKrEP53I04TcHCyyhcK4I9voXg==", "requires": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.30.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-generic-pool": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.39.0.tgz", - "integrity": "sha512-y4v8Y+tSfRB3NNBvHjbjrn7rX/7sdARG7FuK6zR8PGb28CTa0kHpEGCJqvL9L8xkTNvTXo+lM36ajFGUaK1aNw==", + "@opentelemetry/instrumentation-net": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.56.0.tgz", + "integrity": "sha512-h69x7U6f86mP3gGWWTaMkQZk0K3tBvpVMIU7E0q2kkVw6eZ5TqFm9rkaEy38moQmixiDFQ9j/2/cwxG9P7ZEeA==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-graphql": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.43.0.tgz", - "integrity": "sha512-aI3YMmC2McGd8KW5du1a2gBA0iOMOGLqg4s9YjzwbjFwjlmMNFSK1P3AIg374GWg823RPUGfVTIgZ/juk9CVOA==", + "@opentelemetry/instrumentation-openai": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-openai/-/instrumentation-openai-0.10.0.tgz", + "integrity": "sha512-0lV2zxge2mMaruVCw/bmypWVu+aJ76rc0HBvAVFCPUI3zzJdgBZJZafGIHZ1IB2F6VvrDFL+JstEnle6V8brvA==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/api-logs": "^0.212.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.36.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-grpc": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-grpc/-/instrumentation-grpc-0.53.0.tgz", - "integrity": "sha512-Ss338T92yE1UCgr9zXSY3cPuaAy27uQw+wAC5IwsQKCXL5wwkiOgkd+2Ngksa9EGsgUEMwGeHi76bDdHFJ5Rrw==", + "@opentelemetry/instrumentation-oracledb": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-oracledb/-/instrumentation-oracledb-0.37.0.tgz", + "integrity": "sha512-OzMghtAEAEkXlkUrZI4QcXSZq0MILeU6WC0/N5+1MSkuIkruIeaRw99/RtyS2of8vlPDa8XbbXl32Q1RM3wSyg==", "requires": { - "@opentelemetry/instrumentation": "0.53.0", - "@opentelemetry/semantic-conventions": "1.27.0" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.34.0", + "@types/oracledb": "6.5.2" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-hapi": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.41.0.tgz", - "integrity": "sha512-jKDrxPNXDByPlYcMdZjNPYCvw0SQJjN+B1A+QH+sx+sAHsKSAf9hwFiJSrI6C4XdOls43V/f/fkp9ITkHhKFbQ==", + "@opentelemetry/instrumentation-pg": { + "version": "0.64.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.64.0.tgz", + "integrity": "sha512-NbfB/rlfsRI3zpTjnbvJv3qwuoGLsN8FxR/XoI+ZTn1Rs62x1IenO+TSSvk4NO+7FlXpd2MiOe8LT/oNbydHGA==", "requires": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.34.0", + "@opentelemetry/sql-common": "^0.41.2", + "@types/pg": "8.15.6", + "@types/pg-pool": "2.0.7" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-http": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.53.0.tgz", - "integrity": "sha512-H74ErMeDuZfj7KgYCTOFGWF5W9AfaPnqLQQxeFq85+D29wwV2yqHbz2IKLYpkOh7EI6QwDEl7rZCIxjJLyc/CQ==", + "@opentelemetry/instrumentation-pino": { + "version": "0.58.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.58.0.tgz", + "integrity": "sha512-rgy+tA7cDjuSq6dXAO40OiYP25azIDHMBtxG3RzSmCBVEYdjggl6btyuLVasX6VkOOhP2gf6PBuLMNxVwaIqAw==", "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/instrumentation": "0.53.0", - "@opentelemetry/semantic-conventions": "1.27.0", - "semver": "^7.5.2" + "@opentelemetry/api-logs": "^0.212.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-ioredis": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.43.0.tgz", - "integrity": "sha512-i3Dke/LdhZbiUAEImmRG3i7Dimm/BD7t8pDDzwepSvIQ6s2X6FPia7561gw+64w+nx0+G9X14D7rEfaMEmmjig==", + "@opentelemetry/instrumentation-redis": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.60.0.tgz", + "integrity": "sha512-Ea/GffmmzIVHc9geaMjT94IR7poVZzIv4Kk/Lw0tbxGD3cBYcMUsLFVajKxpZsE1NRCECFpidAWeifCIKD0inw==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/redis-common": "^0.36.2", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/redis-common": "^0.38.2", "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-kafkajs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.3.0.tgz", - "integrity": "sha512-UnkZueYK1ise8FXQeKlpBd7YYUtC7mM8J0wzUSccEfc/G8UqHQqAzIyYCUOUPUKp8GsjLnWOOK/3hJc4owb7Jg==", + "@opentelemetry/instrumentation-restify": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.57.0.tgz", + "integrity": "sha512-kO6MsZFU+RdXOKhsKw8SOSBYGYCdFSlza+mpBQRl1DQmveZcnidchv4V5JQPtNgHxCGH+1n3hDpLdxdGUbJPNA==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-knex": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.40.0.tgz", - "integrity": "sha512-6jka2jfX8+fqjEbCn6hKWHVWe++mHrIkLQtaJqUkBt3ZBs2xn1+y0khxiDS0v/mNb0bIKDJWwtpKFfsQDM1Geg==", + "@opentelemetry/instrumentation-router": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.56.0.tgz", + "integrity": "sha512-PHECDGQElLazI/QbHU16C5m9fDC7DGJk+jLIwO5ca6bcp7bXhUPPUTT78l7da2pDsrz4mhv5ytYNZmBbW/Q3rA==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/instrumentation": "^0.212.0", "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-koa": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.43.0.tgz", - "integrity": "sha512-lDAhSnmoTIN6ELKmLJBplXzT/Jqs5jGZehuG22EdSMaTwgjMpxMDI1YtlKEhiWPWkrz5LUsd0aOO0ZRc9vn3AQ==", + "@opentelemetry/instrumentation-runtime-node": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-runtime-node/-/instrumentation-runtime-node-0.25.0.tgz", + "integrity": "sha512-XaCmwBSui5KeTn8M6OzaEn1rEsNWtUkjuc1ylg0tqQTLHibNQ0n7f8v4zdF6x/nBV1OnsiYlN8RLHauGemv/TA==", "requires": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/instrumentation": "^0.212.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-lru-memoizer": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.40.0.tgz", - "integrity": "sha512-21xRwZsEdMPnROu/QsaOIODmzw59IYpGFmuC4aFWvMj6stA8+Ei1tX67nkarJttlNjoM94um0N4X26AD7ff54A==", + "@opentelemetry/instrumentation-socket.io": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.59.0.tgz", + "integrity": "sha512-71DnM/FEqH0PjvU2uZvzWJeaGyVIy3rJKk8rZrxg/aS2QT3qLGb+UPL/B+1vOw4pzDPn4papLTSMpLVF9G8uvw==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/instrumentation": "^0.212.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-memcached": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-memcached/-/instrumentation-memcached-0.39.0.tgz", - "integrity": "sha512-WfwvKAZ9I1qILRP5EUd88HQjwAAL+trXpCpozjBi4U6a0A07gB3fZ5PFAxbXemSjF5tHk9KVoROnqHvQ+zzFSQ==", + "@opentelemetry/instrumentation-tedious": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.31.0.tgz", + "integrity": "sha512-HoF2EtcyP3JR4R3jLPHohZ9lFcj1QLJyGmFfLKDTvUUjPiFuK4XZ6L1OV9HhaqvN0xY+tWKfNdCPS3r33rd0Xw==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/memcached": "^2.2.6" + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/tedious": "^4.0.14" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-mongodb": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.47.0.tgz", - "integrity": "sha512-yqyXRx2SulEURjgOQyJzhCECSh5i1uM49NUaq9TqLd6fA7g26OahyJfsr9NE38HFqGRHpi4loyrnfYGdrsoVjQ==", + "@opentelemetry/instrumentation-undici": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.22.0.tgz", + "integrity": "sha512-yb6vEWUPOrD5i7yR1XceEEqiVHbMgr5YnUPnom5eQVCjvrTkEVswyrf9i+vvJR+28wrNqILIIphWgOOx6BjnTQ==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/sdk-metrics": "^1.9.1", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.24.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-mongoose": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.42.0.tgz", - "integrity": "sha512-AnWv+RaR86uG3qNEMwt3plKX1ueRM7AspfszJYVkvkehiicC3bHQA6vWdb6Zvy5HAE14RyFbu9+2hUUjR2NSyg==", + "@opentelemetry/instrumentation-winston": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.56.0.tgz", + "integrity": "sha512-ITIA0Qe61CQ6FQU/bN23pNBvJ+5U0ofoASMOOYrODtXyV9wI267AigNTTwDmv2Myt8dPEFvvVFJZKhiZLIpehA==", "requires": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/api-logs": "^0.212.0", + "@opentelemetry/instrumentation": "^0.212.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "requires": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + } + } } }, - "@opentelemetry/instrumentation-mysql": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.41.0.tgz", - "integrity": "sha512-jnvrV6BsQWyHS2qb2fkfbfSb1R/lmYwqEZITwufuRl37apTopswu9izc0b1CYRp/34tUG/4k/V39PND6eyiNvw==", + "@opentelemetry/otlp-exporter-base": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.213.0.tgz", + "integrity": "sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/mysql": "2.15.26" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-transformer": "0.213.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } } }, - "@opentelemetry/instrumentation-mysql2": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.41.0.tgz", - "integrity": "sha512-REQB0x+IzVTpoNgVmy5b+UnH1/mDByrneimP6sbDHkp1j8QOl1HyWOrBH/6YWR0nrbU3l825Em5PlybjT3232g==", + "@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.213.0.tgz", + "integrity": "sha512-XgRGuLE9usFNlnw2lgMIM4HTwpcIyjdU/xPoJ8v3LbBLBfjaDkIugjc9HoWa7ZSJ/9Bhzgvm/aD0bGdYUFgnTw==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@opentelemetry/sql-common": "^0.40.1" + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } } }, - "@opentelemetry/instrumentation-nestjs-core": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.40.0.tgz", - "integrity": "sha512-WF1hCUed07vKmf5BzEkL0wSPinqJgH7kGzOjjMAiTGacofNXjb/y4KQ8loj2sNsh5C/NN7s1zxQuCgbWbVTGKg==", + "@opentelemetry/otlp-transformer": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.213.0.tgz", + "integrity": "sha512-RSuAlxFFPjeK4d5Y6ps8L2WhaQI6CXWllIjvo5nkAlBpmq2XdYWEBGiAbOF4nDs8CX4QblJDv5BbMUft3sEfDw==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/api-logs": "0.213.0", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-logs": "0.213.0", + "@opentelemetry/sdk-metrics": "2.6.0", + "@opentelemetry/sdk-trace-base": "2.6.0", + "protobufjs": "^7.0.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.213.0.tgz", + "integrity": "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/sdk-metrics": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.0.tgz", + "integrity": "sha512-CicxWZxX6z35HR83jl+PLgtFgUrKRQ9LCXyxgenMnz5A1lgYWfAog7VtdOvGkJYyQgMNPhXQwkYrDLujk7z1Iw==", + "requires": { + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0" + } + } } }, - "@opentelemetry/instrumentation-net": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-net/-/instrumentation-net-0.39.0.tgz", - "integrity": "sha512-rixHoODfI/Cx1B0mH1BpxCT0bRSxktuBDrt9IvpT2KSEutK5hR0RsRdgdz/GKk+BQ4u+IG6godgMSGwNQCueEA==", + "@opentelemetry/propagator-b3": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.6.0.tgz", + "integrity": "sha512-SguK4jMmRvQ0c0dxAMl6K+Eu1+01X0OP7RLiIuHFjOS8hlB23ZYNnhnbAdSQEh5xVXQmH0OAS0TnmVI+6vB2Kg==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/core": "2.6.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } } }, - "@opentelemetry/instrumentation-pg": { - "version": "0.44.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.44.0.tgz", - "integrity": "sha512-oTWVyzKqXud1BYEGX1loo2o4k4vaU1elr3vPO8NZolrBtFvQ34nx4HgUaexUDuEog00qQt+MLR5gws/p+JXMLQ==", + "@opentelemetry/propagator-jaeger": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.6.0.tgz", + "integrity": "sha512-KGWJuvp9X8X36bhHgIhWEnHAzXDInFr+Fvo9IQhhuu6pXLT8mF7HzFyx/X+auZUITvPaZhM39Phj3vK12MbhwA==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@opentelemetry/sql-common": "^0.40.1", - "@types/pg": "8.6.1", - "@types/pg-pool": "2.0.6" + "@opentelemetry/core": "2.6.0" }, "dependencies": { - "@types/pg": { - "version": "8.6.1", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", - "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", "requires": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" + "@opentelemetry/semantic-conventions": "^1.29.0" } } } }, - "@opentelemetry/instrumentation-pino": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pino/-/instrumentation-pino-0.42.0.tgz", - "integrity": "sha512-SoX6FzucBfTuFNMZjdurJhcYWq2ve8/LkhmyVLUW31HpIB45RF1JNum0u4MkGisosDmXlK4njomcgUovShI+WA==", + "@opentelemetry/redis-common": { + "version": "0.38.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz", + "integrity": "sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==" + }, + "@opentelemetry/resource-detector-alibaba-cloud": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.33.2.tgz", + "integrity": "sha512-EaS54zwYmOg9Ttc79juaktpCBYqyh2IquXl534sLls+c1/pc8LZfWPMqytFt+iBvSPQ6ajraUnvi6cun4AhSjQ==", "requires": { - "@opentelemetry/api-logs": "^0.53.0", - "@opentelemetry/core": "^1.25.0", - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0" } }, - "@opentelemetry/instrumentation-redis": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.42.0.tgz", - "integrity": "sha512-jZBoqve0rEC51q0HuhjtZVq1DtUvJHzEJ3YKGvzGar2MU1J4Yt5+pQAQYh1W4jSoDyKeaI4hyeUdWM5N0c2lqA==", + "@opentelemetry/resource-detector-aws": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-2.12.0.tgz", + "integrity": "sha512-VelueKblsnQEiBVqEYcvM9VEb+B8zN6nftltdO9HAD7qi/OlicP4z/UGJ9EeW2m++WabdMoj0G3QVL8YV0P9tw==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/redis-common": "^0.36.2", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.27.0" } }, - "@opentelemetry/instrumentation-redis-4": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.42.0.tgz", - "integrity": "sha512-NaD+t2JNcOzX/Qa7kMy68JbmoVIV37fT/fJYzLKu2Wwd+0NCxt+K2OOsOakA8GVg8lSpFdbx4V/suzZZ2Pvdjg==", + "@opentelemetry/resource-detector-azure": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-azure/-/resource-detector-azure-0.20.0.tgz", + "integrity": "sha512-iRy+O2cB6DOlQ/OONaK+L8Cp8nLS89dZVRp6KgnFAfzykXuq9Ws/ygJKcU3CCmjkgY5j2Vk3uVTre/E35bWhYg==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/redis-common": "^0.36.2", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/semantic-conventions": "^1.37.0" } }, - "@opentelemetry/instrumentation-restify": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-restify/-/instrumentation-restify-0.41.0.tgz", - "integrity": "sha512-gKEo+X/wVKUBuD2WDDlF7SlDNBHMWjSQoLxFCsGqeKgHR0MGtwMel8uaDGg9LJ83nKqYy+7Vl/cDFxjba6H+/w==", + "@opentelemetry/resource-detector-container": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.8.3.tgz", + "integrity": "sha512-5J0JP2cy655rBKM9Doz26ffO3rG+Xqm7OXeNXkckzmc3JmL6Bj3dPBKugPYsfemhEIqtf7INH9UmPQqTMuWoHg==", "requires": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0" } }, - "@opentelemetry/instrumentation-router": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-router/-/instrumentation-router-0.40.0.tgz", - "integrity": "sha512-bRo4RaclGFiKtmv/N1D0MuzO7DuxbeqMkMCbPPng6mDwzpHAMpHz/K/IxJmF+H1Hi/NYXVjCKvHGClageLe9eA==", + "@opentelemetry/resource-detector-gcp": { + "version": "0.47.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.47.0.tgz", + "integrity": "sha512-57T/kRVdU0ch1P4KPEkmU2b5mWNlUs8hHgqrBYVF+fNZMc1jMdL1mANZhEzoLtWKIeoCEy+57Itt7RkXAYNJiQ==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/resources": "^2.0.0", + "gcp-metadata": "^8.0.0" } }, - "@opentelemetry/instrumentation-socket.io": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-socket.io/-/instrumentation-socket.io-0.42.0.tgz", - "integrity": "sha512-xB5tdsBzuZyicQTO3hDzJIpHQ7V1BYJ6vWPWgl19gWZDBdjEGc3HOupjkd3BUJyDoDhbMEHGk2nNlkUU99EfkA==", + "@opentelemetry/resources": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.0.tgz", + "integrity": "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } } }, - "@opentelemetry/instrumentation-tedious": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.14.0.tgz", - "integrity": "sha512-ofq7pPhSqvRDvD2FVx3RIWPj76wj4QubfrbqJtEx0A+fWoaYxJOCIQ92tYJh28elAmjMmgF/XaYuJuBhBv5J3A==", + "@opentelemetry/sdk-logs": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.213.0.tgz", + "integrity": "sha512-00xlU3GZXo3kXKve4DLdrAL0NAFUaZ9appU/mn00S/5kSUdAvyYsORaDUfR04Mp2CLagAOhrzfUvYozY/EZX2g==", "requires": { - "@opentelemetry/instrumentation": "^0.53.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/tedious": "^4.0.14" + "@opentelemetry/api-logs": "0.213.0", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.213.0.tgz", + "integrity": "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } } }, - "@opentelemetry/instrumentation-undici": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.6.0.tgz", - "integrity": "sha512-ABJBhm5OdhGmbh0S/fOTE4N69IZ00CsHC5ijMYfzbw3E5NwLgpQk5xsljaECrJ8wz1SfXbO03FiSuu5AyRAkvQ==", + "@opentelemetry/sdk-metrics": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.0.tgz", + "integrity": "sha512-Vd7h95av/LYRsAVN7wbprvvJnHkq7swMXAo7Uad0Uxf9jl6NSReLa0JNivrcc5BVIx/vl2t+cgdVQQbnVhsR9w==", + "requires": { + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.0.tgz", + "integrity": "sha512-DT12SXVwV2eoJrGf4nnsvZojxxeQo+LlNAsoYGRRObPWTeN6APiqZ2+nqDCQDvQX40eLi1AePONS0onoASp3yQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/resources": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.0.tgz", + "integrity": "sha512-K+oi0hNMv94EpZbnW3eyu2X6SGVpD3O5DhG2NIp65Hc7lhAj9brRXTAVzh3wB82+q3ThakEf7Zd7RsFUqcTc7A==", + "requires": { + "@opentelemetry/core": "2.7.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } + } + }, + "@opentelemetry/sdk-node": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.213.0.tgz", + "integrity": "sha512-8s7SQtY8DIAjraXFrUf0+I90SBAUQbsMWMtUGKmusswRHWXtKJx42aJQMoxEtC82Csqj+IlBH6FoP8XmmUDSrQ==", + "requires": { + "@opentelemetry/api-logs": "0.213.0", + "@opentelemetry/configuration": "0.213.0", + "@opentelemetry/context-async-hooks": "2.6.0", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/exporter-logs-otlp-grpc": "0.213.0", + "@opentelemetry/exporter-logs-otlp-http": "0.213.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.213.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.213.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.213.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.213.0", + "@opentelemetry/exporter-prometheus": "0.213.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.213.0", + "@opentelemetry/exporter-trace-otlp-http": "0.213.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.213.0", + "@opentelemetry/exporter-zipkin": "2.6.0", + "@opentelemetry/instrumentation": "0.213.0", + "@opentelemetry/propagator-b3": "2.6.0", + "@opentelemetry/propagator-jaeger": "2.6.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-logs": "0.213.0", + "@opentelemetry/sdk-metrics": "2.6.0", + "@opentelemetry/sdk-trace-base": "2.6.0", + "@opentelemetry/sdk-trace-node": "2.6.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.213.0.tgz", + "integrity": "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.213.0.tgz", + "integrity": "sha512-Z8gYKUAU48qwm+a1tjnGv9xbE7a5lukVIwgF6Z5i3VPXPVMe4Sjra0nN3zU7m277h+V+ZpsPGZJ2Xf0OTkL7/w==", + "requires": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.213.0", + "@opentelemetry/otlp-exporter-base": "0.213.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.213.0", + "@opentelemetry/otlp-transformer": "0.213.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/sdk-metrics": "2.6.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.213.0.tgz", + "integrity": "sha512-3i9NdkET/KvQomeh7UaR/F4r9P25Rx6ooALlWXPIjypcEOUxksCmVu0zA70NBJWlrMW1rPr/LRidFAflLI+s/w==", + "requires": { + "@opentelemetry/api-logs": "0.213.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" + } + }, + "@opentelemetry/sdk-metrics": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.0.tgz", + "integrity": "sha512-CicxWZxX6z35HR83jl+PLgtFgUrKRQ9LCXyxgenMnz5A1lgYWfAog7VtdOvGkJYyQgMNPhXQwkYrDLujk7z1Iw==", + "requires": { + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0" + } + }, + "cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==" + }, + "import-in-the-middle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.0.tgz", + "integrity": "sha512-OnGy+eYT7wVejH2XWgLRgbmzujhhVIATQH0ztIeRilwHBjTeG3pD+XnH3PKX0r9gJ0BuJmJ68q/oh9qgXnNDQg==", + "requires": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + } + } + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.0.tgz", + "integrity": "sha512-g/OZVkqlxllgFM7qMKqbPV9c1DUPhQ7d4n3pgZFcrnrNft9eJXZM2TNHTPYREJBrtNdRytYyvwjgL5geDKl3EQ==", "requires": { - "@opentelemetry/core": "^1.8.0", - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } } }, - "@opentelemetry/instrumentation-winston": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-winston/-/instrumentation-winston-0.40.0.tgz", - "integrity": "sha512-eMk2tKl86YJ8/yHvtDbyhrE35/R0InhO9zuHTflPx8T0+IvKVUhPV71MsJr32sImftqeOww92QHt4Jd+a5db4g==", + "@opentelemetry/sdk-trace-node": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.6.0.tgz", + "integrity": "sha512-YhswtasmsbIGEFvLGvR9p/y3PVRTfFf+mgY8van4Ygpnv4sA3vooAjvh+qAn9PNWxs4/IwGGqiQS0PPsaRJ0vQ==", "requires": { - "@opentelemetry/api-logs": "^0.53.0", - "@opentelemetry/instrumentation": "^0.53.0" + "@opentelemetry/context-async-hooks": "2.6.0", + "@opentelemetry/core": "2.6.0", + "@opentelemetry/sdk-trace-base": "2.6.0" + }, + "dependencies": { + "@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + } } }, - "@opentelemetry/otlp-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.53.0.tgz", - "integrity": "sha512-UCWPreGQEhD6FjBaeDuXhiMf6kkBODF0ZQzrk/tuQcaVDJ+dDQ/xhJp192H9yWnKxVpEjFrSSLnpqmX4VwX+eA==", - "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-transformer": "0.53.0" - } + "@opentelemetry/semantic-conventions": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.38.0.tgz", + "integrity": "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==" }, - "@opentelemetry/otlp-grpc-exporter-base": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.53.0.tgz", - "integrity": "sha512-F7RCN8VN+lzSa4fGjewit8Z5fEUpY/lmMVy5EWn2ZpbAabg3EE3sCLuTNfOiooNGnmvzimUPruoeqeko/5/TzQ==", + "@opentelemetry/sql-common": { + "version": "0.41.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.41.2.tgz", + "integrity": "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==", "requires": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/otlp-exporter-base": "0.53.0", - "@opentelemetry/otlp-transformer": "0.53.0" + "@opentelemetry/core": "^2.0.0" } }, - "@opentelemetry/otlp-transformer": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.53.0.tgz", - "integrity": "sha512-rM0sDA9HD8dluwuBxLetUmoqGJKSAbWenwD65KY9iZhUxdBHRLrIdrABfNDP7aiTjcgK8XFyTn5fhDz7N+W6DA==", - "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-metrics": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "protobufjs": "^7.3.0" - } + "@oxc-project/types": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", + "dev": true }, - "@opentelemetry/propagation-utils": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagation-utils/-/propagation-utils-0.30.11.tgz", - "integrity": "sha512-rY4L/2LWNk5p/22zdunpqVmgz6uN419DsRTw5KFMa6u21tWhXS8devlMy4h8m8nnS20wM7r6yYweCNNKjgLYJw==", - "requires": {} + "@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==" }, - "@opentelemetry/propagator-aws-xray": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-aws-xray/-/propagator-aws-xray-1.26.0.tgz", - "integrity": "sha512-Sex+JyEZ/xX328TArBqQjh1NZSfNyw5NdASUIi9hnPsnMBMSBaDe7B9JRnXv0swz7niNyAnXa6MY7yOCV76EvA==", + "@platformatic/basic": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/basic/-/basic-3.52.0.tgz", + "integrity": "sha512-wXn4/Y6kSr11iXOIsEbCBEfWXm23FdK++y8bJ07MqnakERn9Riac2IIXgZm8QHDWbUHWDrQBQBdEqWsg//3luA==", "requires": { - "@opentelemetry/core": "1.26.0" + "@fastify/error": "^4.0.0", + "@platformatic/foundation": "3.52.0", + "@platformatic/itc": "3.52.0", + "@platformatic/metrics": "3.52.0", + "@platformatic/telemetry": "3.52.0", + "execa": "^9.3.1", + "fast-json-patch": "^3.1.1", + "pino": "^9.9.0", + "pino-abstract-transport": "^2.0.0", + "semver": "^7.6.3", + "split2": "^4.2.0", + "undici": "^7.0.0", + "ws": "^8.18.0" + }, + "dependencies": { + "execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "requires": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + } + }, + "get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "requires": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + } + }, + "human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==" + }, + "is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==" + }, + "npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "requires": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + } + }, + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==" + }, + "pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "requires": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + } + }, + "pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "requires": { + "split2": "^4.0.0" + } + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" + }, + "strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==" + }, + "thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "requires": { + "real-require": "^0.2.0" + } + } } }, - "@opentelemetry/propagator-b3": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.26.0.tgz", - "integrity": "sha512-vvVkQLQ/lGGyEy9GT8uFnI047pajSOVnZI2poJqVGD3nJ+B9sFGdlHNnQKophE3lHfnIH0pw2ubrCTjZCgIj+Q==", + "@platformatic/control": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/control/-/control-3.52.0.tgz", + "integrity": "sha512-hZmOnOcoe2/QjrdXUHMrVwb4Nqp/Ba2yXqffz/QCPT1IZ1zAmG28bNYX3PLWFZ+h+TkALU5onHVQjm1Hu7pNzQ==", "requires": { - "@opentelemetry/core": "1.26.0" + "@fastify/error": "^4.0.0", + "@platformatic/foundation": "3.52.0", + "help-me": "^5.0.0", + "pino": "^9.9.0", + "pino-pretty": "^13.0.0", + "table": "^6.8.1", + "undici": "^7.0.0", + "ws": "^8.16.0" + }, + "dependencies": { + "pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "requires": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + } + }, + "pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "requires": { + "split2": "^4.0.0" + } + }, + "thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "requires": { + "real-require": "^0.2.0" + } + } } }, - "@opentelemetry/propagator-jaeger": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.26.0.tgz", - "integrity": "sha512-DelFGkCdaxA1C/QA0Xilszfr0t4YbGd3DjxiCDPh34lfnFr+VkkrjV9S8ZTJvAzfdKERXhfOxIKBoGPJwoSz7Q==", + "@platformatic/flame": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@platformatic/flame/-/flame-1.6.0.tgz", + "integrity": "sha512-xVPFOznGrW+YZw3KbLTeoQsTy/XlDmfqkdbp8p7LZZLFJHnA0FhfMLxGbOrtTaAwVrs3Zp0r0fGTJ06O+AlJ0g==", "requires": { - "@opentelemetry/core": "1.26.0" + "@datadog/pprof": "^5.9.0", + "fastify": "^5.5.0", + "pprof-format": "^2.2.1", + "pprof-to-md": "^0.1.0", + "react-pprof": "^1.4.0" } }, - "@opentelemetry/redis-common": { - "version": "0.36.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz", - "integrity": "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==" - }, - "@opentelemetry/resource-detector-alibaba-cloud": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-alibaba-cloud/-/resource-detector-alibaba-cloud-0.29.1.tgz", - "integrity": "sha512-Qshebw6azBuKUqGkVgambZlLS6Xh+LCoLXep1oqW1RSzSOHQxGYDsPs99v8NzO65eJHHOu8wc2yKsWZQAgYsSw==", + "@platformatic/foundation": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/foundation/-/foundation-3.52.0.tgz", + "integrity": "sha512-w2tw/Cm8VLq8UtvrMz5cDpL+PM40D3xJgU3WcPSnQ3iSnfzPBjrAIpL/YwY8ueiIgUs0s8LfjWe4LSgq8QdUMg==", "requires": { - "@opentelemetry/resources": "^1.0.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@fastify/deepmerge": "^2.0.0", + "@fastify/error": "^4.0.0", + "@iarna/toml": "^2.2.5", + "@watchable/unpromise": "^1.0.2", + "ajv": "^8.12.0", + "boring-name-generator": "^1.0.3", + "colorette": "^2.0.19", + "debug": "^4.4.3", + "fast-json-patch": "^3.1.1", + "json5": "^2.2.3", + "leven": "~3.1.0", + "pino": "^9.9.0", + "pino-pretty": "^13.0.0", + "semver": "^7.6.3", + "undici": "7.24.0", + "yaml": "^2.4.1" + }, + "dependencies": { + "@fastify/deepmerge": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-2.0.2.tgz", + "integrity": "sha512-3wuLdX5iiiYeZWP6bQrjqhrcvBIf0NHbQH1Ur1WbHvoiuTYUEItgygea3zs8aHpiitn0lOB8gX20u1qO+FDm7Q==" + }, + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "requires": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + } + }, + "pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "requires": { + "split2": "^4.0.0" + } + }, + "thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "requires": { + "real-require": "^0.2.0" + } + }, + "undici": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.0.tgz", + "integrity": "sha512-jxytwMHhsbdpBXxLAcuu0fzlQeXCNnWdDyRHpvWsUl8vd98UwYdl9YTyn8/HcpcJPC3pwUveefsa3zTxyD/ERg==" + } } }, - "@opentelemetry/resource-detector-aws": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-aws/-/resource-detector-aws-1.6.1.tgz", - "integrity": "sha512-A/3lqx9xoew7sFi+AVUUVr6VgB7UJ5qqddkKR3gQk9hWLm1R7HUXVJG09cLcZ7DMNpX13DohPRGmHE/vp1vafw==", + "@platformatic/generators": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/generators/-/generators-3.52.0.tgz", + "integrity": "sha512-J+0bs9vce+PGpyzVTwAnVWiXrjEItAYxX7bhzVVX0P0u0BXERi0sy0bklKjBR5tuY16NnteLczuAk0V6xk07/A==", "requires": { - "@opentelemetry/core": "^1.0.0", - "@opentelemetry/resources": "^1.10.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@fastify/error": "^4.0.0", + "@platformatic/foundation": "3.52.0", + "change-case-all": "^2.1.0", + "execa": "^9.6.0", + "fastify": "^5.7.0", + "pino": "^9.9.0", + "undici": "^7.0.0" + }, + "dependencies": { + "execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "requires": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + } + }, + "get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "requires": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + } + }, + "human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==" + }, + "is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==" + }, + "npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "requires": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + } + }, + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==" + }, + "pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "requires": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + } + }, + "pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "requires": { + "split2": "^4.0.0" + } + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" + }, + "strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==" + }, + "thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "requires": { + "real-require": "^0.2.0" + } + } } }, - "@opentelemetry/resource-detector-azure": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-azure/-/resource-detector-azure-0.2.11.tgz", - "integrity": "sha512-XepvQfTXWyHAoAziCfXGwYbSZL0LHtFk5iuKKN2VE2vzcoiw5Tepi0Qafuwb7CCtpQRReao4H7E29MFbCmh47g==", + "@platformatic/globals": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/globals/-/globals-3.52.0.tgz", + "integrity": "sha512-l/UI72POTtP1kTGBTiwZKqjdjwKFSEfI1f5xDgRyWpowwzax2Ud+75ZQqqyPWf5SmupNUNpV5KEi0zUOuT0ZCg==", "requires": { - "@opentelemetry/core": "^1.25.1", - "@opentelemetry/resources": "^1.10.1", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@platformatic/prom-client": "^1.0.0", + "pino": "^9.9.0" + }, + "dependencies": { + "pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "requires": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + } + }, + "pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "requires": { + "split2": "^4.0.0" + } + }, + "thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "requires": { + "real-require": "^0.2.0" + } + } } }, - "@opentelemetry/resource-detector-container": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-container/-/resource-detector-container-0.4.1.tgz", - "integrity": "sha512-v0bvO6RxYtbxvY/HwqrPQnZ4UtP4nBq4AOyS30iqV2vEtiLTY1gNTbNvTF1lwN/gg/g5CY1tRSrHcYODDOv0vw==", + "@platformatic/http-metrics": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@platformatic/http-metrics/-/http-metrics-0.3.0.tgz", + "integrity": "sha512-e+wQJDCd9v7yKeV4u30ibAe5uGB14k+jDw1w9FqsM3tDgFY0UEHb24hJR9j2bVa6091e+ppGy3L4LhyUR92M0w==", "requires": { - "@opentelemetry/resources": "^1.10.0", - "@opentelemetry/semantic-conventions": "^1.27.0" + "@platformatic/prom-client": "^1.0.0" } }, - "@opentelemetry/resource-detector-gcp": { - "version": "0.29.11", - "resolved": "https://registry.npmjs.org/@opentelemetry/resource-detector-gcp/-/resource-detector-gcp-0.29.11.tgz", - "integrity": "sha512-07wJx4nyxD/c2z3n70OQOg8fmoO/baTsq8uU+f7tZaehRNQx76MPkRbV2L902N40Z21SPIG8biUZ30OXE9tOIg==", + "@platformatic/itc": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/itc/-/itc-3.52.0.tgz", + "integrity": "sha512-XwkT/9K5Mk6Br7xDqH9Zpun73n9GwIiIDEXJXgXPzq0Ek2IvUtaSDhWh02WiCIEvIHHFWXHauTd5olkL8eePbg==", "requires": { - "@opentelemetry/core": "^1.0.0", - "@opentelemetry/resources": "^1.0.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "gcp-metadata": "^6.0.0" + "@fastify/error": "^4.0.0", + "@watchable/unpromise": "^1.0.2" } }, - "@opentelemetry/resources": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.26.0.tgz", - "integrity": "sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw==", + "@platformatic/metrics": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/metrics/-/metrics-3.52.0.tgz", + "integrity": "sha512-uFCWl1l/0WGtlhmwVQmb4c4ozCZl0xiDuskuZd//xYslhX+UUxs5XGVqFAr4PYJuYQyjooOs22WyXcqX6564xQ==", "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" + "@platformatic/http-metrics": "^0.3.0", + "@platformatic/prom-client": "^1.0.0", + "@platformatic/promotel": "^0.1.0" } }, - "@opentelemetry/sdk-logs": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.53.0.tgz", - "integrity": "sha512-dhSisnEgIj/vJZXZV6f6KcTnyLDx/VuQ6l3ejuZpMpPlh9S1qMHiZU9NMmOkVkwwHkMy3G6mEBwdP23vUZVr4g==", + "@platformatic/node": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/node/-/node-3.52.0.tgz", + "integrity": "sha512-d/F0kmgOHUe2/ZfqQChgbRclCEer+4LZRcFzCiOZyTnbaqs4Ffe2XVRFCemA/EwxvMisgIvKtVF6g5Y82iwc7g==", "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" + "@platformatic/basic": "3.52.0", + "@platformatic/foundation": "3.52.0", + "@platformatic/generators": "3.52.0", + "@watchable/unpromise": "^1.0.2", + "json5": "^2.2.3", + "light-my-request": "^6.0.0" } }, - "@opentelemetry/sdk-metrics": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz", - "integrity": "sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ==", + "@platformatic/prom-client": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@platformatic/prom-client/-/prom-client-1.0.0.tgz", + "integrity": "sha512-O7NfmdBWAm1QJ0LMrtcyCSgWmA+FQEiCyRqvouccmyAuydwLxLdmhcTTW3sEmO5f7bRfXpUVVgTtnFbIqEiHyw==", "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0" + "@opentelemetry/api": "^1.4.0" } }, - "@opentelemetry/sdk-node": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.53.0.tgz", - "integrity": "sha512-0hsxfq3BKy05xGktwG8YdGdxV978++x40EAKyKr1CaHZRh8uqVlXnclnl7OMi9xLMJEcXUw7lGhiRlArFcovyg==", - "requires": { - "@opentelemetry/api-logs": "0.53.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/exporter-logs-otlp-grpc": "0.53.0", - "@opentelemetry/exporter-logs-otlp-http": "0.53.0", - "@opentelemetry/exporter-logs-otlp-proto": "0.53.0", - "@opentelemetry/exporter-trace-otlp-grpc": "0.53.0", - "@opentelemetry/exporter-trace-otlp-http": "0.53.0", - "@opentelemetry/exporter-trace-otlp-proto": "0.53.0", - "@opentelemetry/exporter-zipkin": "1.26.0", - "@opentelemetry/instrumentation": "0.53.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/sdk-logs": "0.53.0", - "@opentelemetry/sdk-metrics": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "@opentelemetry/sdk-trace-node": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" + "@platformatic/promotel": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@platformatic/promotel/-/promotel-0.1.0.tgz", + "integrity": "sha512-PpbXGiGef+zW9LAbD3JP1ehTDhRXXcrBz+cg1fd5kZamMqqf3leXSjOBlagqxaMk8e1M1GggnmB4RxGBvoAcGQ==", + "requires": { + "long": "^5.3.2", + "prom-client": "^15.1.3", + "protobufjs": "^7.4.0", + "undici": "^6.19.8" + }, + "dependencies": { + "undici": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", + "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==" + } } }, - "@opentelemetry/sdk-trace-base": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz", - "integrity": "sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw==", + "@platformatic/runtime": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/runtime/-/runtime-3.52.0.tgz", + "integrity": "sha512-/aC8MafEm6dornRlPZoaLlPGHfs/K1JLHgdg1zoC/yg6DpPfo/6MopD2hEb9Hm3aHdTJolmSZ77yzpS2sOes9Q==", "requires": { - "@opentelemetry/core": "1.26.0", - "@opentelemetry/resources": "1.26.0", - "@opentelemetry/semantic-conventions": "1.27.0" + "@fastify/accepts": "^5.0.0", + "@fastify/basic-auth": "^6.0.0", + "@fastify/error": "^4.0.0", + "@fastify/websocket": "^11.0.0", + "@opentelemetry/api": "^1.9.0", + "@platformatic/basic": "3.52.0", + "@platformatic/foundation": "3.52.0", + "@platformatic/generators": "3.52.0", + "@platformatic/itc": "3.52.0", + "@platformatic/metrics": "3.52.0", + "@platformatic/prom-client": "^1.0.0", + "@platformatic/telemetry": "3.52.0", + "@platformatic/undici-cache-memory": "^0.8.1", + "@watchable/unpromise": "^1.0.2", + "change-case-all": "^2.1.0", + "close-with-grace": "^2.3.0", + "colorette": "^2.0.20", + "cron": "^4.1.0", + "debounce": "^2.0.0", + "fastest-levenshtein": "^1.0.16", + "fastify": "^5.7.0", + "graphql": "^16.8.1", + "help-me": "^5.0.0", + "minimist": "^1.2.8", + "pino": "^10.1.0", + "pino-opentelemetry-transport": "^2.0.0", + "pino-pretty": "^13.0.0", + "semgrator": "^0.3.0", + "sonic-boom": "^4.2.0", + "systeminformation": "^5.27.11", + "undici": "^7.0.0", + "undici-thread-interceptor": "^1.3.1", + "ws": "^8.16.0" + }, + "dependencies": { + "colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + } } }, - "@opentelemetry/sdk-trace-node": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.26.0.tgz", - "integrity": "sha512-Fj5IVKrj0yeUwlewCRwzOVcr5avTuNnMHWf7GPc1t6WaT78J6CJyF3saZ/0RkZfdeNO8IcBl/bNcWMVZBMRW8Q==", + "@platformatic/telemetry": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/@platformatic/telemetry/-/telemetry-3.52.0.tgz", + "integrity": "sha512-QVdy0L0mDnRRhO5w1PJVMaZyx0TluLrN6pe8Lpsu7qBkfoAhxplGMDfxrscu7yXhnNur4X4U4IGGk0Vm5yiDkA==", "requires": { - "@opentelemetry/context-async-hooks": "1.26.0", - "@opentelemetry/core": "1.26.0", - "@opentelemetry/propagator-b3": "1.26.0", - "@opentelemetry/propagator-jaeger": "1.26.0", - "@opentelemetry/sdk-trace-base": "1.26.0", - "semver": "^7.5.2" + "@fastify/swagger": "^9.5.1", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^2.0.1", + "@opentelemetry/exporter-trace-otlp-proto": "^0.203.0", + "@opentelemetry/exporter-zipkin": "^2.0.1", + "@opentelemetry/instrumentation": "^0.203.0", + "@opentelemetry/instrumentation-http": "^0.203.0", + "@opentelemetry/instrumentation-undici": "^0.14.0", + "@opentelemetry/resources": "^2.0.1", + "@opentelemetry/sdk-node": "^0.203.0", + "@opentelemetry/sdk-trace-base": "^2.0.1", + "@opentelemetry/semantic-conventions": "1.36.0", + "@platformatic/foundation": "3.52.0", + "fast-uri": "^3.0.6", + "fastify-plugin": "^5.0.1" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.203.0.tgz", + "integrity": "sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/context-async-hooks": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.0.1.tgz", + "integrity": "sha512-XuY23lSI3d4PEqKA+7SLtAgwqIfc6E/E9eAQWLN1vlpC53ybO3o6jW4BsXo1xvz9lYyyWItfQDDLzezER01mCw==", + "requires": {} + }, + "@opentelemetry/core": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", + "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.203.0.tgz", + "integrity": "sha512-g/2Y2noc/l96zmM+g0LdeuyYKINyBwN6FJySoU15LHPLcMN/1a0wNk2SegwKcxrRdE7Xsm7fkIR5n6XFe3QpPw==", + "requires": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/sdk-logs": "0.203.0" + } + }, + "@opentelemetry/exporter-logs-otlp-http": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.203.0.tgz", + "integrity": "sha512-s0hys1ljqlMTbXx2XiplmMJg9wG570Z5lH7wMvrZX6lcODI56sG4HL03jklF63tBeyNwK2RV1/ntXGo3HgG4Qw==", + "requires": { + "@opentelemetry/api-logs": "0.203.0", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/sdk-logs": "0.203.0" + } + }, + "@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.203.0.tgz", + "integrity": "sha512-nl/7S91MXn5R1aIzoWtMKGvqxgJgepB/sH9qW0rZvZtabnsjbf8OQ1uSx3yogtvLr0GzwD596nQKz2fV7q2RBw==", + "requires": { + "@opentelemetry/api-logs": "0.203.0", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-logs": "0.203.0", + "@opentelemetry/sdk-trace-base": "2.0.1" + } + }, + "@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.203.0.tgz", + "integrity": "sha512-FCCj9nVZpumPQSEI57jRAA89hQQgONuoC35Lt+rayWY/mzCAc6BQT7RFyFaZKJ2B7IQ8kYjOCPsF/HGFWjdQkQ==", + "requires": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.203.0", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-metrics": "2.0.1" + } + }, + "@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.203.0.tgz", + "integrity": "sha512-HFSW10y8lY6BTZecGNpV3GpoSy7eaO0Z6GATwZasnT4bEsILp8UJXNG5OmEsz4SdwCSYvyCbTJdNbZP3/8LGCQ==", + "requires": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-metrics": "2.0.1" + } + }, + "@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.203.0.tgz", + "integrity": "sha512-OZnhyd9npU7QbyuHXFEPVm3LnjZYifuKpT3kTnF84mXeEQ84pJJZgyLBpU4FSkSwUkt/zbMyNAI7y5+jYTWGIg==", + "requires": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.203.0", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-metrics": "2.0.1" + } + }, + "@opentelemetry/exporter-prometheus": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.203.0.tgz", + "integrity": "sha512-2jLuNuw5m4sUj/SncDf/mFPabUxMZmmYetx5RKIMIQyPnl6G6ooFzfeE8aXNRf8YD1ZXNlCnRPcISxjveGJHNg==", + "requires": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-metrics": "2.0.1" + } + }, + "@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.203.0.tgz", + "integrity": "sha512-322coOTf81bm6cAA8+ML6A+m4r2xTCdmAZzGNTboPXRzhwPt4JEmovsFAs+grpdarObd68msOJ9FfH3jxM6wqA==", + "requires": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1" + } + }, + "@opentelemetry/exporter-trace-otlp-http": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.203.0.tgz", + "integrity": "sha512-ZDiaswNYo0yq/cy1bBLJFe691izEJ6IgNmkjm4C6kE9ub/OMQqDXORx2D2j8fzTBTxONyzusbaZlqtfmyqURPw==", + "requires": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1" + } + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.203.0.tgz", + "integrity": "sha512-1xwNTJ86L0aJmWRwENCJlH4LULMG2sOXWIVw+Szta4fkqKVY50Eo4HoVKKq6U9QEytrWCr8+zjw0q/ZOeXpcAQ==", + "requires": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1" + } + }, + "@opentelemetry/exporter-zipkin": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.0.1.tgz", + "integrity": "sha512-a9eeyHIipfdxzCfc2XPrE+/TI3wmrZUDFtG2RRXHSbZZULAny7SyybSvaDvS77a7iib5MPiAvluwVvbGTsHxsw==", + "requires": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.203.0.tgz", + "integrity": "sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ==", + "requires": { + "@opentelemetry/api-logs": "0.203.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1" + } + }, + "@opentelemetry/instrumentation-http": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.203.0.tgz", + "integrity": "sha512-y3uQAcCOAwnO6vEuNVocmpVzG3PER6/YZqbPbbffDdJ9te5NkHEkfSMNzlC3+v7KlE+WinPGc3N7MR30G1HY2g==", + "requires": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/instrumentation": "0.203.0", + "@opentelemetry/semantic-conventions": "^1.29.0", + "forwarded-parse": "2.1.2" + } + }, + "@opentelemetry/instrumentation-undici": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.14.0.tgz", + "integrity": "sha512-2HN+7ztxAReXuxzrtA3WboAKlfP5OsPA57KQn2AdYZbJ3zeRPcLXyW4uO/jpLE6PLm0QRtmeGCmfYpqRlwgSwg==", + "requires": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.203.0" + } + }, + "@opentelemetry/otlp-exporter-base": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.203.0.tgz", + "integrity": "sha512-Wbxf7k+87KyvxFr5D7uOiSq/vHXWommvdnNE7vECO3tAhsA2GfOlpWINCMWUEPdHZ7tCXxw6Epp3vgx3jU7llQ==", + "requires": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-transformer": "0.203.0" + } + }, + "@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.203.0.tgz", + "integrity": "sha512-te0Ze1ueJF+N/UOFl5jElJW4U0pZXQ8QklgSfJ2linHN0JJsuaHG8IabEUi2iqxY8ZBDlSiz1Trfv5JcjWWWwQ==", + "requires": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.203.0", + "@opentelemetry/otlp-transformer": "0.203.0" + } + }, + "@opentelemetry/otlp-transformer": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.203.0.tgz", + "integrity": "sha512-Y8I6GgoCna0qDQ2W6GCRtaF24SnvqvA8OfeTi7fqigD23u8Jpb4R5KFv/pRvrlGagcCLICMIyh9wiejp4TXu/A==", + "requires": { + "@opentelemetry/api-logs": "0.203.0", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-logs": "0.203.0", + "@opentelemetry/sdk-metrics": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1", + "protobufjs": "^7.3.0" + } + }, + "@opentelemetry/propagator-b3": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.0.1.tgz", + "integrity": "sha512-Hc09CaQ8Tf5AGLmf449H726uRoBNGPBL4bjr7AnnUpzWMvhdn61F78z9qb6IqB737TffBsokGAK1XykFEZ1igw==", + "requires": { + "@opentelemetry/core": "2.0.1" + } + }, + "@opentelemetry/propagator-jaeger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.0.1.tgz", + "integrity": "sha512-7PMdPBmGVH2eQNb/AtSJizQNgeNTfh6jQFqys6lfhd6P4r+m/nTh3gKPPpaCXVdRQ+z93vfKk+4UGty390283w==", + "requires": { + "@opentelemetry/core": "2.0.1" + } + }, + "@opentelemetry/resources": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", + "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", + "requires": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/sdk-logs": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.203.0.tgz", + "integrity": "sha512-vM2+rPq0Vi3nYA5akQD2f3QwossDnTDLvKbea6u/A2NZ3XDkPxMfo/PNrDoXhDUD/0pPo2CdH5ce/thn9K0kLw==", + "requires": { + "@opentelemetry/api-logs": "0.203.0", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1" + } + }, + "@opentelemetry/sdk-metrics": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", + "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", + "requires": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1" + } + }, + "@opentelemetry/sdk-node": { + "version": "0.203.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.203.0.tgz", + "integrity": "sha512-zRMvrZGhGVMvAbbjiNQW3eKzW/073dlrSiAKPVWmkoQzah9wfynpVPeL55f9fVIm0GaBxTLcPeukWGy0/Wj7KQ==", + "requires": { + "@opentelemetry/api-logs": "0.203.0", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/exporter-logs-otlp-grpc": "0.203.0", + "@opentelemetry/exporter-logs-otlp-http": "0.203.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.203.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.203.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.203.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.203.0", + "@opentelemetry/exporter-prometheus": "0.203.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.203.0", + "@opentelemetry/exporter-trace-otlp-http": "0.203.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.203.0", + "@opentelemetry/exporter-zipkin": "2.0.1", + "@opentelemetry/instrumentation": "0.203.0", + "@opentelemetry/propagator-b3": "2.0.1", + "@opentelemetry/propagator-jaeger": "2.0.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-logs": "0.203.0", + "@opentelemetry/sdk-metrics": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1", + "@opentelemetry/sdk-trace-node": "2.0.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz", + "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", + "requires": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/sdk-trace-node": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.0.1.tgz", + "integrity": "sha512-UhdbPF19pMpBtCWYP5lHbTogLWx9N0EBxtdagvkn5YtsAnCBZzL7SjktG+ZmupRgifsHMjwUaCCaVmqGfSADmA==", + "requires": { + "@opentelemetry/context-async-hooks": "2.0.1", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1" + } + }, + "@opentelemetry/semantic-conventions": { + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.36.0.tgz", + "integrity": "sha512-TtxJSRD8Ohxp6bKkhrm27JRHAxPczQA7idtcTOMYI+wQRRrfgqxHv1cFbCApcSnNjtXkmzFozn6jQtFrOmbjPQ==" + }, + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "requires": { + "ms": "^2.1.3" + } + }, + "import-in-the-middle": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz", + "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==", + "requires": { + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "require-in-the-middle": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", + "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", + "requires": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" + } + } } }, - "@opentelemetry/semantic-conventions": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz", - "integrity": "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==" + "@platformatic/undici-cache-memory": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@platformatic/undici-cache-memory/-/undici-cache-memory-0.8.4.tgz", + "integrity": "sha512-/JVfPhyUW0GQkmr5lGAGOgh0lpJbTcLG8VB2uNqgNgH1fhPPFAM0l4pLSyeDzpTj2r/GJPLqhzvCgangnSHfoQ==" }, - "@opentelemetry/sql-common": { - "version": "0.40.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz", - "integrity": "sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==", + "@platformatic/wattpm-pprof-capture": { + "version": "3.52.1", + "resolved": "https://registry.npmjs.org/@platformatic/wattpm-pprof-capture/-/wattpm-pprof-capture-3.52.1.tgz", + "integrity": "sha512-OHcHxSKmDba6RURkN4vwAoCE2H5zJULhhlntKOiJo5MdUK4Z5j8gVXvj/Zee5Nr4QWwYyNq4kwgoGCLLPC5tcw==", "requires": { - "@opentelemetry/core": "^1.1.0" + "@datadog/pprof": "^5.3.0", + "@fastify/error": "^4.0.0", + "undici": "^7.0.0" } }, - "@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "optional": true - }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -31727,35 +22812,149 @@ "yallist": "4.0.0" } }, - "@shopify/semaphore": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@shopify/semaphore/-/semaphore-3.1.0.tgz", - "integrity": "sha512-LxonkiWEu12FbZhuOMhsdocpxCqm7By8C/2U9QgNuEoXUx2iMrlXjJv3p93RwfNC6TrdlNRo17gRer1z1309VQ==" + "@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", + "dev": true, + "optional": true }, - "@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true + "@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", + "dev": true, + "optional": true }, - "@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", "dev": true, - "requires": { - "type-detect": "4.0.8" - } + "optional": true }, - "@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", + "dev": true, + "optional": true + }, + "@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", "dev": true, + "optional": true + }, + "@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", + "dev": true, + "optional": true, "requires": { - "@sinonjs/commons": "^3.0.0" + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" + }, + "dependencies": { + "@napi-rs/wasm-runtime": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", + "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", + "dev": true, + "optional": true, + "requires": { + "@tybys/wasm-util": "^0.10.1" + } + } } }, + "@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", + "dev": true, + "optional": true + }, + "@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", + "dev": true, + "optional": true + }, + "@rolldown/pluginutils": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", + "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", + "dev": true + }, + "@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==" + }, + "@shopify/semaphore": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@shopify/semaphore/-/semaphore-3.1.0.tgz", + "integrity": "sha512-LxonkiWEu12FbZhuOMhsdocpxCqm7By8C/2U9QgNuEoXUx2iMrlXjJv3p93RwfNC6TrdlNRo17gRer1z1309VQ==" + }, + "@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==" + }, "@smithy/abort-controller": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.1.1.tgz", @@ -31766,94 +22965,141 @@ } }, "@smithy/chunked-blob-reader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-4.0.0.tgz", - "integrity": "sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.2.tgz", + "integrity": "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==", "requires": { "tslib": "^2.6.2" } }, "@smithy/chunked-blob-reader-native": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.1.tgz", - "integrity": "sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.3.tgz", + "integrity": "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==", "requires": { - "@smithy/util-base64": "^3.0.0", + "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "@smithy/config-resolver": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.12.tgz", - "integrity": "sha512-YAJP9UJFZRZ8N+UruTeq78zkdjUHmzsY62J4qKWZ4SXB4QXJ/+680EfXXgkYA2xj77ooMqtUY9m406zGNqwivQ==", + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.13.tgz", + "integrity": "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg==", "requires": { - "@smithy/node-config-provider": "^3.1.11", - "@smithy/types": "^3.7.1", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.10", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", "requires": { + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } } } }, "@smithy/core": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.5.4.tgz", - "integrity": "sha512-iFh2Ymn2sCziBRLPuOOxRPkuCx/2gBdXtBGuCUFLUe6bWYjKnhHyIPqGeNkLZ5Aco/5GjebRTBFiWID3sDbrKw==", - "requires": { - "@smithy/middleware-serde": "^3.0.10", - "@smithy/protocol-http": "^4.1.7", - "@smithy/types": "^3.7.1", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-middleware": "^3.0.10", - "@smithy/util-stream": "^3.3.1", - "@smithy/util-utf8": "^3.0.0", + "version": "3.23.13", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.13.tgz", + "integrity": "sha512-J+2TT9D6oGsUVXVEMvz8h2EmdVnkBiy2auCie4aSJMvKlzUtO5hqjEzXhoCUkIMo7gAYjbQcN0g/MMSXEhDs1Q==", + "requires": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-stream": "^4.5.21", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" }, "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "requires": { + "tslib": "^2.6.2" + } + }, "@smithy/protocol-http": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", - "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "requires": { - "@smithy/types": "^3.7.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "requires": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", "requires": { + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } } } }, "@smithy/credential-provider-imds": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.7.tgz", - "integrity": "sha512-cEfbau+rrWF8ylkmmVAObOmjbTIzKyUC5TkBL58SbLywD0RCBC4JAUKbmtSm2w5KUJNRPGgpGFMvE2FKnuNlWQ==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.12.tgz", + "integrity": "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==", "requires": { - "@smithy/node-config-provider": "^3.1.11", - "@smithy/property-provider": "^3.1.10", - "@smithy/types": "^3.7.1", - "@smithy/url-parser": "^3.0.10", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -31861,20 +23107,28 @@ } }, "@smithy/eventstream-codec": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.9.tgz", - "integrity": "sha512-F574nX0hhlNOjBnP+noLtsPFqXnWh2L0+nZKCwcu7P7J8k+k+rdIDs+RMnrMwrzhUE4mwMgyN0cYnEn0G8yrnQ==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.12.tgz", + "integrity": "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA==", "requires": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^3.7.1", - "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/types": "^4.13.1", + "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", "requires": { "tslib": "^2.6.2" } @@ -31882,19 +23136,19 @@ } }, "@smithy/eventstream-serde-browser": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.13.tgz", - "integrity": "sha512-Nee9m+97o9Qj6/XeLz2g2vANS2SZgAxV4rDBMKGHvFJHU/xz88x2RwCkwsvEwYjSX4BV1NG1JXmxEaDUzZTAtw==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.12.tgz", + "integrity": "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A==", "requires": { - "@smithy/eventstream-serde-universal": "^3.0.12", - "@smithy/types": "^3.7.1", + "@smithy/eventstream-serde-universal": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -31902,18 +23156,18 @@ } }, "@smithy/eventstream-serde-config-resolver": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.10.tgz", - "integrity": "sha512-K1M0x7P7qbBUKB0UWIL5KOcyi6zqV5mPJoL0/o01HPJr0CSq3A9FYuJC6e11EX6hR8QTIR++DBiGrYveOu6trw==", + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.12.tgz", + "integrity": "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q==", "requires": { - "@smithy/types": "^3.7.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -31921,19 +23175,19 @@ } }, "@smithy/eventstream-serde-node": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.12.tgz", - "integrity": "sha512-kiZymxXvZ4tnuYsPSMUHe+MMfc4FTeFWJIc0Q5wygJoUQM4rVHNghvd48y7ppuulNMbuYt95ah71pYc2+o4JOA==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.12.tgz", + "integrity": "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA==", "requires": { - "@smithy/eventstream-serde-universal": "^3.0.12", - "@smithy/types": "^3.7.1", + "@smithy/eventstream-serde-universal": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -31941,19 +23195,19 @@ } }, "@smithy/eventstream-serde-universal": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.12.tgz", - "integrity": "sha512-1i8ifhLJrOZ+pEifTlF0EfZzMLUGQggYQ6WmZ4d5g77zEKf7oZ0kvh1yKWHPjofvOwqrkwRDVuxuYC8wVd662A==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.12.tgz", + "integrity": "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ==", "requires": { - "@smithy/eventstream-codec": "^3.1.9", - "@smithy/types": "^3.7.1", + "@smithy/eventstream-codec": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -31961,48 +23215,48 @@ } }, "@smithy/fetch-http-handler": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.8.tgz", - "integrity": "sha512-Lqe0B8F5RM7zkw//6avq1SJ8AfaRd3ubFUS1eVp5WszV7p6Ne5hQ4dSuMHDpNRPhgTvj4va9Kd/pcVigHEHRow==", + "version": "5.3.15", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.15.tgz", + "integrity": "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==", "requires": { - "@smithy/protocol-http": "^4.1.3", - "@smithy/querystring-builder": "^3.0.6", - "@smithy/types": "^3.4.2", - "@smithy/util-base64": "^3.0.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" }, "dependencies": { "@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "requires": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "@smithy/querystring-builder": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.6.tgz", - "integrity": "sha512-sQe08RunoObe+Usujn9+R2zrLuQERi3CWvRO3BvnoWSYUaIrLKuAIeY7cMeDax6xGyfIP3x/yFWbEKSXvOnvVg==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", "requires": { - "@smithy/types": "^3.4.2", - "@smithy/util-uri-escape": "^3.0.0", + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } }, "@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", "requires": { "tslib": "^2.6.2" } @@ -32010,20 +23264,20 @@ } }, "@smithy/hash-blob-browser": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.9.tgz", - "integrity": "sha512-wOu78omaUuW5DE+PVWXiRKWRZLecARyP3xcq5SmkXUw9+utgN8HnSnBfrjL2B/4ZxgqPjaAJQkC/+JHf1ITVaQ==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.13.tgz", + "integrity": "sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g==", "requires": { - "@smithy/chunked-blob-reader": "^4.0.0", - "@smithy/chunked-blob-reader-native": "^3.0.1", - "@smithy/types": "^3.7.1", + "@smithy/chunked-blob-reader": "^5.2.2", + "@smithy/chunked-blob-reader-native": "^4.2.3", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32031,59 +23285,111 @@ } }, "@smithy/hash-node": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.10.tgz", - "integrity": "sha512-3zWGWCHI+FlJ5WJwx73Mw2llYR8aflVyZN5JhoqLxbdPZi6UyKSdCeXAWJw9ja22m6S6Tzz1KZ+kAaSwvydi0g==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.12.tgz", + "integrity": "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==", "requires": { - "@smithy/types": "^3.7.1", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/types": "^4.13.1", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "requires": { + "tslib": "^2.6.2" + } + }, "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", "requires": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "requires": { + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } } } }, "@smithy/hash-stream-node": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.9.tgz", - "integrity": "sha512-3XfHBjSP3oDWxLmlxnt+F+FqXpL3WlXs+XXaB6bV9Wo8BBu87fK1dSEsyH7Z4ZHRmwZ4g9lFMdf08m9hoX1iRA==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.12.tgz", + "integrity": "sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw==", "requires": { - "@smithy/types": "^3.7.1", - "@smithy/util-utf8": "^3.0.0", + "@smithy/types": "^4.13.1", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "requires": { + "tslib": "^2.6.2" + } + }, "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", "requires": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "requires": { + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } } } }, "@smithy/invalid-dependency": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.10.tgz", - "integrity": "sha512-Lp2L65vFi+cj0vFMu2obpPW69DU+6O5g3086lmI4XcnRCG8PxvpWC7XyaVwJCxsZFzueHjXnrOH/E0pl0zikfA==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.12.tgz", + "integrity": "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==", "requires": { - "@smithy/types": "^3.7.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32099,48 +23405,74 @@ } }, "@smithy/md5-js": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.10.tgz", - "integrity": "sha512-m3bv6dApflt3fS2Y1PyWPUtRP7iuBlvikEOGwu0HsCZ0vE7zcIX+dBoh3e+31/rddagw8nj92j0kJg2TfV+SJA==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.12.tgz", + "integrity": "sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ==", "requires": { - "@smithy/types": "^3.7.1", - "@smithy/util-utf8": "^3.0.0", + "@smithy/types": "^4.13.1", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "requires": { + "tslib": "^2.6.2" + } + }, "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", "requires": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "requires": { + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } } } }, "@smithy/middleware-content-length": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.12.tgz", - "integrity": "sha512-1mDEXqzM20yywaMDuf5o9ue8OkJ373lSPbaSjyEvkWdqELhFMyNNgKGWL/rCSf4KME8B+HlHKuR8u9kRj8HzEQ==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.12.tgz", + "integrity": "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==", "requires": { - "@smithy/protocol-http": "^4.1.7", - "@smithy/types": "^3.7.1", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/protocol-http": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", - "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "requires": { - "@smithy/types": "^3.7.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32148,78 +23480,107 @@ } }, "@smithy/middleware-endpoint": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.2.4.tgz", - "integrity": "sha512-TybiW2LA3kYVd3e+lWhINVu1o26KJbBwOpADnf0L4x/35vLVica77XVR5hvV9+kWeTGeSJ3IHTcYxbRxlbwhsg==", + "version": "4.4.28", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.28.tgz", + "integrity": "sha512-p1gfYpi91CHcs5cBq982UlGlDrxoYUX6XdHSo91cQ2KFuz6QloHosO7Jc60pJiVmkWrKOV8kFYlGFFbQ2WUKKQ==", "requires": { - "@smithy/core": "^2.5.4", - "@smithy/middleware-serde": "^3.0.10", - "@smithy/node-config-provider": "^3.1.11", - "@smithy/shared-ini-file-loader": "^3.1.11", - "@smithy/types": "^3.7.1", - "@smithy/url-parser": "^3.0.10", - "@smithy/util-middleware": "^3.0.10", + "@smithy/core": "^3.23.13", + "@smithy/middleware-serde": "^4.2.16", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", "requires": { + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } } } }, "@smithy/middleware-retry": { - "version": "3.0.28", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.28.tgz", - "integrity": "sha512-vK2eDfvIXG1U64FEUhYxoZ1JSj4XFbYWkK36iz02i3pFwWiDz1Q7jKhGTBCwx/7KqJNk4VS7d7cDLXFOvP7M+g==", + "version": "4.4.46", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.46.tgz", + "integrity": "sha512-SpvWNNOPOrKQGUqZbEPO+es+FRXMWvIyzUKUOYdDgdlA6BdZj/R58p4umoQ76c2oJC44PiM7mKizyyex1IJzow==", "requires": { - "@smithy/node-config-provider": "^3.1.11", - "@smithy/protocol-http": "^4.1.7", - "@smithy/service-error-classification": "^3.0.10", - "@smithy/smithy-client": "^3.4.5", - "@smithy/types": "^3.7.1", - "@smithy/util-middleware": "^3.0.10", - "@smithy/util-retry": "^3.0.10", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/service-error-classification": "^4.2.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.13", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" }, "dependencies": { "@smithy/protocol-http": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", - "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "requires": { - "@smithy/types": "^3.7.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", "requires": { + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } } } }, "@smithy/middleware-serde": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.10.tgz", - "integrity": "sha512-MnAuhh+dD14F428ubSJuRnmRsfOpxSzvRhaGVTvd/lrUDE3kxzCCmH8lnVTvoNQnV2BbJ4c15QwZ3UdQBtFNZA==", + "version": "4.2.16", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.16.tgz", + "integrity": "sha512-beqfV+RZ9RSv+sQqor3xroUUYgRFCGRw6niGstPG8zO9LgTl0B0MCucxjmrH/2WwksQN7UUgI7KNANoZv+KALA==", "requires": { - "@smithy/types": "^3.7.1", + "@smithy/core": "^3.23.13", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "requires": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32227,18 +23588,18 @@ } }, "@smithy/middleware-stack": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.10.tgz", - "integrity": "sha512-grCHyoiARDBBGPyw2BeicpjgpsDFWZZxptbVKb3CRd/ZA15F/T6rZjCCuBUjJwdck1nwUuIxYtsS4H9DDpbP5w==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.12.tgz", + "integrity": "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==", "requires": { - "@smithy/types": "^3.7.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32246,20 +23607,20 @@ } }, "@smithy/node-config-provider": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.11.tgz", - "integrity": "sha512-URq3gT3RpDikh/8MBJUB+QGZzfS7Bm6TQTqoh4CqE8NBuyPkWa5eUXj0XFcFfeZVgg3WMh1u19iaXn8FvvXxZw==", + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.12.tgz", + "integrity": "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==", "requires": { - "@smithy/property-provider": "^3.1.10", - "@smithy/shared-ini-file-loader": "^3.1.11", - "@smithy/types": "^3.7.1", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32279,18 +23640,18 @@ } }, "@smithy/property-provider": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.10.tgz", - "integrity": "sha512-n1MJZGTorTH2DvyTVj+3wXnd4CzjJxyXeOgnTlgNVFxaaMeT4OteEp4QrzF8p9ee2yg42nvyVK6R/awLCakjeQ==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.12.tgz", + "integrity": "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==", "requires": { - "@smithy/types": "^3.7.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32317,18 +23678,18 @@ } }, "@smithy/querystring-parser": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.10.tgz", - "integrity": "sha512-Oa0XDcpo9SmjhiDD9ua2UyM3uU01ZTuIrNdZvzwUTykW1PM8o2yJvMh1Do1rY5sUQg4NDV70dMi0JhDx4GyxuQ==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.12.tgz", + "integrity": "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==", "requires": { - "@smithy/types": "^3.7.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32336,17 +23697,17 @@ } }, "@smithy/service-error-classification": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.10.tgz", - "integrity": "sha512-zHe642KCqDxXLuhs6xmHVgRwy078RfqxP2wRDpIyiF8EmsWXptMwnMwbVa50lw+WOGNrYm9zbaEg0oDe3PTtvQ==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.12.tgz", + "integrity": "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==", "requires": { - "@smithy/types": "^3.7.1" + "@smithy/types": "^4.13.1" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32354,18 +23715,18 @@ } }, "@smithy/shared-ini-file-loader": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.11.tgz", - "integrity": "sha512-AUdrIZHFtUgmfSN4Gq9nHu3IkHMa1YDcN+s061Nfm+6pQ0mJy85YQDB0tZBCmls0Vuj22pLwDPmL92+Hvfwwlg==", + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.7.tgz", + "integrity": "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==", "requires": { - "@smithy/types": "^3.7.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32373,74 +23734,117 @@ } }, "@smithy/signature-v4": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.3.tgz", - "integrity": "sha512-pPSQQ2v2vu9vc8iew7sszLd0O09I5TRc5zhY71KA+Ao0xYazIG+uLeHbTJfIWGO3BGVLiXjUr3EEeCcEQLjpWQ==", + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.12.tgz", + "integrity": "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==", "requires": { - "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.1.7", - "@smithy/types": "^3.7.1", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.10", - "@smithy/util-uri-escape": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "requires": { + "tslib": "^2.6.2" + } + }, "@smithy/protocol-http": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", - "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "requires": { - "@smithy/types": "^3.7.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "requires": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", "requires": { + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", "requires": { + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } } } }, "@smithy/smithy-client": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.5.tgz", - "integrity": "sha512-k0sybYT9zlP79sIKd1XGm4TmK0AS1nA2bzDHXx7m0nGi3RQ8dxxQUs4CPkSmQTKAo+KF9aINU3KzpGIpV7UoMw==", + "version": "4.12.8", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.8.tgz", + "integrity": "sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA==", "requires": { - "@smithy/core": "^2.5.4", - "@smithy/middleware-endpoint": "^3.2.4", - "@smithy/middleware-stack": "^3.0.10", - "@smithy/protocol-http": "^4.1.7", - "@smithy/types": "^3.7.1", - "@smithy/util-stream": "^3.3.1", + "@smithy/core": "^3.23.13", + "@smithy/middleware-endpoint": "^4.4.28", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-stream": "^4.5.21", "tslib": "^2.6.2" }, "dependencies": { "@smithy/protocol-http": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", - "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "requires": { - "@smithy/types": "^3.7.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32448,26 +23852,27 @@ } }, "@smithy/types": { - "version": "https://registry.npmjs.org/@smithy/types/-/types-2.9.1.tgz", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.9.1.tgz", "integrity": "sha512-vjXlKNXyprDYDuJ7UW5iobdmyDm6g8dDG+BFUncAg/3XJaN45Gy5RWWWUVgrzIK7S4R1KWgIX5LeJcfvSI24bw==", "requires": { "tslib": "^2.5.0" } }, "@smithy/url-parser": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.10.tgz", - "integrity": "sha512-j90NUalTSBR2NaZTuruEgavSdh8MLirf58LoGSk4AtQfyIymogIhgnGUU2Mga2bkMkpSoC9gxb74xBXL5afKAQ==", + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.12.tgz", + "integrity": "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==", "requires": { - "@smithy/querystring-parser": "^3.0.10", - "@smithy/types": "^3.7.1", + "@smithy/querystring-parser": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32475,27 +23880,55 @@ } }, "@smithy/util-base64": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", - "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", "requires": { - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "requires": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "requires": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + } + } } }, "@smithy/util-body-length-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", - "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", "requires": { "tslib": "^2.6.2" } }, "@smithy/util-body-length-node": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", - "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", "requires": { "tslib": "^2.6.2" } @@ -32510,29 +23943,28 @@ } }, "@smithy/util-config-provider": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", - "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", "requires": { "tslib": "^2.6.2" } }, "@smithy/util-defaults-mode-browser": { - "version": "3.0.28", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.28.tgz", - "integrity": "sha512-6bzwAbZpHRFVJsOztmov5PGDmJYsbNSoIEfHSJJyFLzfBGCCChiO3od9k7E/TLgrCsIifdAbB9nqbVbyE7wRUw==", + "version": "4.3.44", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.44.tgz", + "integrity": "sha512-eZg6XzaCbVr2S5cAErU5eGBDaOVTuTo1I65i4tQcHENRcZ8rMWhQy1DaIYUSLyZjsfXvmCqZrstSMYyGFocvHA==", "requires": { - "@smithy/property-provider": "^3.1.10", - "@smithy/smithy-client": "^3.4.5", - "@smithy/types": "^3.7.1", - "bowser": "^2.11.0", + "@smithy/property-provider": "^4.2.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32540,23 +23972,23 @@ } }, "@smithy/util-defaults-mode-node": { - "version": "3.0.28", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.28.tgz", - "integrity": "sha512-78ENJDorV1CjOQselGmm3+z7Yqjj5HWCbjzh0Ixuq736dh1oEnD9sAttSBNSLlpZsX8VQnmERqA2fEFlmqWn8w==", + "version": "4.2.48", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.48.tgz", + "integrity": "sha512-FqOKTlqSaoV3nzO55pMs5NBnZX8EhoI0DGmn9kbYeXWppgHD6dchyuj2HLqp4INJDJbSrj6OFYJkAh/WhSzZPg==", "requires": { - "@smithy/config-resolver": "^3.0.12", - "@smithy/credential-provider-imds": "^3.2.7", - "@smithy/node-config-provider": "^3.1.11", - "@smithy/property-provider": "^3.1.10", - "@smithy/smithy-client": "^3.4.5", - "@smithy/types": "^3.7.1", + "@smithy/config-resolver": "^4.4.13", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/smithy-client": "^4.12.8", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32564,19 +23996,19 @@ } }, "@smithy/util-endpoints": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.6.tgz", - "integrity": "sha512-mFV1t3ndBh0yZOJgWxO9J/4cHZVn5UG1D8DeCc6/echfNkeEJWu9LD7mgGH5fHrEdR7LDoWw7PQO6QiGpHXhgA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.3.tgz", + "integrity": "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==", "requires": { - "@smithy/node-config-provider": "^3.1.11", - "@smithy/types": "^3.7.1", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32592,18 +24024,18 @@ } }, "@smithy/util-middleware": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.10.tgz", - "integrity": "sha512-eJO+/+RsrG2RpmY68jZdwQtnfsxjmPxzMlQpnHKjFPwrYqvlcT+fHdT+ZVwcjlWSrByOhGr9Ff2GG17efc192A==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.11.tgz", + "integrity": "sha512-dWpyc1e1R6VoXrwLoLDd57U1z6CwNSdkM69Ie4+6uYh2GC7Vg51Qtan7ITzczuVpqezdDTKJGJB95fFvvjU/ow==", "requires": { - "@smithy/types": "^3.7.1", + "@smithy/types": "^3.7.2", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz", + "integrity": "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg==", "requires": { "tslib": "^2.6.2" } @@ -32611,19 +24043,19 @@ } }, "@smithy/util-retry": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.10.tgz", - "integrity": "sha512-1l4qatFp4PiU6j7UsbasUHL2VU023NRB/gfaa1M0rDqVrRN4g3mCArLRyH3OuktApA4ye+yjWQHjdziunw2eWA==", + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.13.tgz", + "integrity": "sha512-qQQsIvL0MGIbUjeSrg0/VlQ3jGNKyM3/2iU3FPNgy01z+Sp4OvcaxbgIoFOTvB61ZoohtutuOvOcgmhbD0katQ==", "requires": { - "@smithy/service-error-classification": "^3.0.10", - "@smithy/types": "^3.7.1", + "@smithy/service-error-classification": "^4.2.12", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } @@ -32631,85 +24063,97 @@ } }, "@smithy/util-stream": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.3.1.tgz", - "integrity": "sha512-Ff68R5lJh2zj+AUTvbAU/4yx+6QPRzg7+pI7M1FbtQHcRIp7xvguxVsQBKyB3fwiOwhAKu0lnNyYBaQfSW6TNw==", - "requires": { - "@smithy/fetch-http-handler": "^4.1.1", - "@smithy/node-http-handler": "^3.3.1", - "@smithy/types": "^3.7.1", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "version": "4.5.21", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.21.tgz", + "integrity": "sha512-KzSg+7KKywLnkoKejRtIBXDmwBfjGvg1U1i/etkC7XSWUyFCoLno1IohV2c74IzQqdhX5y3uE44r/8/wuK+A7Q==", + "requires": { + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/node-http-handler": "^4.5.1", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, "dependencies": { - "@smithy/abort-controller": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.8.tgz", - "integrity": "sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==", + "@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/node-http-handler": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.1.tgz", + "integrity": "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw==", + "requires": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", "requires": { - "@smithy/types": "^3.7.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, - "@smithy/fetch-http-handler": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-4.1.1.tgz", - "integrity": "sha512-bH7QW0+JdX0bPBadXt8GwMof/jz0H28I84hU1Uet9ISpzUqXqRQ3fEZJ+ANPOhzSEczYvANNl3uDQDYArSFDtA==", + "@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", "requires": { - "@smithy/protocol-http": "^4.1.7", - "@smithy/querystring-builder": "^3.0.10", - "@smithy/types": "^3.7.1", - "@smithy/util-base64": "^3.0.0", + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, - "@smithy/node-http-handler": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.1.tgz", - "integrity": "sha512-fr+UAOMGWh6bn4YSEezBCpJn9Ukp9oR4D32sCjCo7U81evE11YePOQ58ogzyfgmjIO79YeOdfXXqr0jyhPQeMg==", - "requires": { - "@smithy/abort-controller": "^3.1.8", - "@smithy/protocol-http": "^4.1.7", - "@smithy/querystring-builder": "^3.0.10", - "@smithy/types": "^3.7.1", + "@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "requires": { "tslib": "^2.6.2" } }, - "@smithy/protocol-http": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.7.tgz", - "integrity": "sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==", + "@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", "requires": { - "@smithy/types": "^3.7.1", + "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, - "@smithy/querystring-builder": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.10.tgz", - "integrity": "sha512-nT9CQF3EIJtIUepXQuBFb8dxJi3WVZS3XfuDksxSCSn+/CzZowRLdhDn+2acbBv8R6eaJqPupoI/aRFIImNVPQ==", + "@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", "requires": { - "@smithy/types": "^3.7.1", - "@smithy/util-uri-escape": "^3.0.0", "tslib": "^2.6.2" } }, - "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", "requires": { "tslib": "^2.6.2" } }, - "@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", "requires": { + "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } } @@ -32733,34 +24177,38 @@ } }, "@smithy/util-waiter": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.9.tgz", - "integrity": "sha512-/aMXPANhMOlMPjfPtSrDfPeVP8l56SJlz93xeiLmhLe5xvlXA5T3abZ2ilEsDEPeY9T/wnN/vNGn9wa1SbufWA==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.14.tgz", + "integrity": "sha512-2zqq5o/oizvMaFUlNiTyZ7dbgYv1a893aGut2uaxtbzTx/VYYnRxWzDHuD/ftgcw94ffenua+ZNLrbqwUYE+Bg==", "requires": { - "@smithy/abort-controller": "^3.1.8", - "@smithy/types": "^3.7.1", + "@smithy/types": "^4.13.1", "tslib": "^2.6.2" }, "dependencies": { - "@smithy/abort-controller": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.8.tgz", - "integrity": "sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==", - "requires": { - "@smithy/types": "^3.7.1", - "tslib": "^2.6.2" - } - }, "@smithy/types": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.1.tgz", - "integrity": "sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "requires": { "tslib": "^2.6.2" } } } }, + "@smithy/uuid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true + }, "@tus/file-store": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tus/file-store/-/file-store-2.0.0.tgz", @@ -32772,11 +24220,11 @@ } }, "@tus/s3-store": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tus/s3-store/-/s3-store-2.0.1.tgz", - "integrity": "sha512-V4hHtQgUWQMVFJJIVRkCrCoSxpDeYrCnqQKf65qYC5fSb5suBmYAApxYJ7AnIkhX6fscx1Lkc86a1+C3a0k/Jg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@tus/s3-store/-/s3-store-2.0.2.tgz", + "integrity": "sha512-wXrlb8k0BikpSs5FuMSTjEOK0QrL+xBEqPrmROcLOWMAaximPHtzHjvG6QDc5UbjBaV8PTqMnWhtXj85vvn3tw==", "requires": { - "@aws-sdk/client-s3": "3.654.0", + "@aws-sdk/client-s3": "^3.758.0", "@shopify/semaphore": "^3.1.0", "@tus/utils": "^0.6.0", "debug": "^4.3.4", @@ -32802,6 +24250,16 @@ "resolved": "https://registry.npmjs.org/@tus/utils/-/utils-0.6.0.tgz", "integrity": "sha512-GpMpAQfVdC4UDhpsZrRPjGpdXg+JW5MquqMqtObUVsORwLBV6XI67iTT5be+z98THdqb6dl3bTLIElIdgPeo2g==" }, + "@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + }, "@types/async-retry": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/@types/async-retry/-/async-retry-1.4.5.tgz", @@ -32812,55 +24270,14 @@ } }, "@types/aws-lambda": { - "version": "8.10.143", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.143.tgz", - "integrity": "sha512-u5vzlcR14ge/4pMTTMDQr3MF0wEe38B2F9o84uC4F43vN5DGTy63npRrB6jQhyt+C0lGv4ZfiRcRkqJoZuPnmg==" - }, - "@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "requires": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.2.tgz", - "integrity": "sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } + "version": "8.10.161", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.161.tgz", + "integrity": "sha512-rUYdp+MQwSFocxIOcSsYSF3YYYC/uUpMbCY/mbO21vGqfrEYvNSoPyKYDj6RhXXpPfS0KstW9RwG3qXh9sL7FQ==" }, "@types/bunyan": { - "version": "1.8.9", - "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.9.tgz", - "integrity": "sha512-ZqS9JGpBxVOvsawzmVt30sP++gSQMTejCkIAQ3VdadOcRE8izTyW66hufvwLeH+YEGP6Js2AW7Gz+RMyvrEbmw==", + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.11.tgz", + "integrity": "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ==", "requires": { "@types/node": "*" } @@ -32874,86 +24291,54 @@ "@types/node": "*" } }, - "@types/connect": { - "version": "3.4.36", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", - "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", - "requires": { - "@types/node": "*" - } - }, - "@types/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==", - "dev": true - }, - "@types/fs-extra": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", - "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "requires": { - "@types/node": "*" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "@types/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "@types/cloneable-readable": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/cloneable-readable/-/cloneable-readable-2.0.3.tgz", + "integrity": "sha512-+Ihof4L4iu9k4WTzYbJSkzUxt6f1wzXn6u48fZYxgST+BsC9bBHTOJ59Buy1/4sC9j7ZWF7bxDf/n/mrtk/nzw==", "dev": true, "requires": { - "@types/minimatch": "^5.1.2", "@types/node": "*" } }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, + "@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "requires": { "@types/node": "*" } }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.1.tgz", - "integrity": "sha512-nKixEdnGDqFOZkMTF74avFNr3yRqB1ZJ6sRZv5/28D5x2oLN14KApv7F9mfDT/vUic0L3tRCsh3XWpWjtJisUQ==", - "dev": true, - "requires": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } + "@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true }, "@types/js-yaml": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", - "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==" + }, + "@types/json-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.4.tgz", + "integrity": "sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag==", "dev": true }, "@types/json-schema": { @@ -32962,6 +24347,11 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "@types/luxon": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz", + "integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==" + }, "@types/memcached": { "version": "2.2.10", "resolved": "https://registry.npmjs.org/@types/memcached/-/memcached-2.2.10.tgz", @@ -32970,47 +24360,43 @@ "@types/node": "*" } }, - "@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true - }, - "@types/multistream": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@types/multistream/-/multistream-4.1.3.tgz", - "integrity": "sha512-t57vmDEJOZuC0M3IrZYfCd9wolTcr3ZTCGk1iwHNosvgBX+7/SMvCGcR8wP9lidpelBZQ12crSuINOxkk0azPA==", - "dev": true, + "@types/mysql": { + "version": "2.15.27", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz", + "integrity": "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==", "requires": { "@types/node": "*" } }, - "@types/mustache": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/mustache/-/mustache-4.2.2.tgz", - "integrity": "sha512-MUSpfpW0yZbTgjekDbH0shMYBUD+X/uJJJMm9LXN1d5yjl5lCY1vN/eWKD6D1tOtjA6206K0zcIPnUaFMurdNA==", - "dev": true + "@types/node": { + "version": "24.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", + "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", + "requires": { + "undici-types": "~7.16.0" + } }, - "@types/mysql": { - "version": "2.15.26", - "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.26.tgz", - "integrity": "sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==", + "@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", "requires": { - "@types/node": "*" + "@types/node": "*", + "form-data": "^4.0.4" } }, - "@types/node": { - "version": "20.11.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.18.tgz", - "integrity": "sha512-ABT5VWnnYneSBcNWYSCuR05M826RoMyMSGiFivXGx6ZUIsXb9vn4643IEwkg2zbEOSgAiSogtapN2fgc4mAPlw==", + "@types/oracledb": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@types/oracledb/-/oracledb-6.5.2.tgz", + "integrity": "sha512-kK1eBS/Adeyis+3OlBDMeQQuasIDLUYXsi2T15ccNJ0iyUpQ4xDF7svFu3+bGVrI0CMBUclPciz+lsQR3JX3TQ==", "requires": { - "undici-types": "~5.26.4" + "@types/node": "*" } }, "@types/pg": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.4.tgz", - "integrity": "sha512-uYA7UMVzDFpJobCrqwW/iWkFmvizy6knIUgr0Quaw7K1Le3ZnF7hI3bKqFoxPZ+fju1Sc7zdTvOl9YfFZPcmeA==", + "version": "8.15.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz", + "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", "requires": { "@types/node": "*", "pg-protocol": "*", @@ -33018,9 +24404,9 @@ } }, "@types/pg-pool": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz", - "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.7.tgz", + "integrity": "sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==", "requires": { "@types/pg": "*" } @@ -33031,38 +24417,14 @@ "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", "dev": true }, - "@types/shimmer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", - "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==" - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, "@types/stream-buffers": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.7.tgz", "integrity": "sha512-azOCy05sXVXrO+qklf0c/B07H/oHaIuDDAiHPVwlk3A9Ek+ksHyTeMajLZl3r76FxpPpxem//4Te61G1iW3Giw==", - "dev": true, "requires": { "@types/node": "*" } }, - "@types/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", - "dev": true - }, - "@types/strip-json-comments": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", - "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", - "dev": true - }, "@types/tedious": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", @@ -33071,11 +24433,6 @@ "@types/node": "*" } }, - "@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==" - }, "@types/xml2js": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", @@ -33085,141 +24442,103 @@ "@types/node": "*" } }, - "@types/yargs": { - "version": "17.0.13", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz", - "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" + "@vitest/coverage-v8": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.4.tgz", + "integrity": "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.4", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "dependencies": { + "@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true + } } }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz", - "integrity": "sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==", + "@vitest/expect": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", + "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", "dev": true, "requires": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.7.0", - "@typescript-eslint/type-utils": "8.7.0", - "@typescript-eslint/utils": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" } }, - "@typescript-eslint/parser": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz", - "integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==", + "@vitest/pretty-format": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", + "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.7.0", - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/typescript-estree": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0", - "debug": "^4.3.4" + "tinyrainbow": "^3.1.0" } }, - "@typescript-eslint/scope-manager": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz", - "integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==", + "@vitest/runner": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", + "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", "dev": true, "requires": { - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0" + "@vitest/utils": "4.1.4", + "pathe": "^2.0.3" } }, - "@typescript-eslint/type-utils": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz", - "integrity": "sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==", + "@vitest/snapshot": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", + "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "8.7.0", - "@typescript-eslint/utils": "8.7.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "@vitest/pretty-format": "4.1.4", + "@vitest/utils": "4.1.4", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" } }, - "@typescript-eslint/types": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz", - "integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==", + "@vitest/spy": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", + "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", "dev": true }, - "@typescript-eslint/typescript-estree": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz", - "integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/visitor-keys": "8.7.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "dependencies": { - "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, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "@typescript-eslint/utils": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz", - "integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==", + "@vitest/utils": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", + "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", "dev": true, "requires": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.7.0", - "@typescript-eslint/types": "8.7.0", - "@typescript-eslint/typescript-estree": "8.7.0" + "@vitest/pretty-format": "4.1.4", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" } }, - "@typescript-eslint/visitor-keys": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz", - "integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "8.7.0", - "eslint-visitor-keys": "^3.4.3" - } + "@watchable/unpromise": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@watchable/unpromise/-/unpromise-1.0.2.tgz", + "integrity": "sha512-yGCKYzCrAfJQ9yzm76r1bl4WUIWyqmh4vqidXn5LyOfPbgdiZrKOyvW2ivqIvtmsRVb7u3ModEpc4q901VRgXw==" }, - "@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "dev": true }, "abort-controller": { @@ -33245,9 +24564,9 @@ } }, "acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==" + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==" }, "acorn-import-attributes": { "version": "1.9.5", @@ -33255,20 +24574,10 @@ "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "requires": {} }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "requires": { - "debug": "^4.3.4" - } + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==" }, "agentkeepalive": { "version": "4.5.0", @@ -33279,14 +24588,14 @@ } }, "ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "requires": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "require-from-string": "^2.0.2" } }, "ajv-formats": { @@ -33303,67 +24612,51 @@ "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } - } - }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "array-ify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==" }, - "async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true }, + "ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + }, + "dependencies": { + "js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true + } + } + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" + }, "async-retry": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", @@ -33383,9 +24676,9 @@ "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" }, "avvio": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.1.0.tgz", - "integrity": "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.2.0.tgz", + "integrity": "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==", "requires": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" @@ -33444,12 +24737,12 @@ } }, "axios": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.1.tgz", - "integrity": "sha512-Kn4kbSXpkFHCGE6rBFNwIv0GQs4AvDT80jlveJDKFxjbTYMUeB4QtsdPCv6H8Cm19Je7IU6VFtRl2zWZI0rudQ==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", "requires": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -33462,119 +24755,63 @@ "is-retry-allowed": "^2.2.0" } }, - "babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "requires": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } + "b4a": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.2.tgz", + "integrity": "sha512-DyUOdz+E8R6+sruDpQNOaV0y/dBbV6X/8ZkxrDcR0Ifc3BgKlpgG0VAtfOozA0eMtJO5GGe9FsZhueLs00pTww==", + "requires": {} }, - "babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } + "bare-events": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.7.0.tgz", + "integrity": "sha512-b3N5eTW1g7vXkw+0CXh/HazGTcO5KYuu/RCNaJbDMPI6LHDi+7qe8EmxKUVe1sUbY2KZOVZFyj62x0OEz9qyAA==" }, - "babel-plugin-polyfill-corejs2": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", - "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", - "dev": true, + "bare-fs": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.4.4.tgz", + "integrity": "sha512-Q8yxM1eLhJfuM7KXVP3zjhBvtMJCYRByoTT+wHXjpdMELv0xICFJX+1w4c7csa+WZEOsq4ItJ4RGwvzid6m/dw==", + "optional": true, "requires": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.4", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" } }, - "babel-plugin-polyfill-corejs3": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", - "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.6.3", - "core-js-compat": "^3.40.0" - } + "bare-os": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "optional": true }, - "babel-plugin-polyfill-regenerator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", - "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", - "dev": true, + "bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "optional": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.6.4" + "bare-os": "^3.0.1" } }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, + "bare-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", + "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "optional": true, "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" + "streamx": "^2.21.0" } }, - "babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, + "bare-url": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.2.2.tgz", + "integrity": "sha512-g+ueNGKkrjMazDG3elZO1pNs3HY5+mMmOet1jtKyhOaCnkLzitxf26z7hoAEkDNgdNmnc1KIlt/dw6Po6xZMpA==", + "optional": true, "requires": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" + "bare-path": "^3.0.0" } }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -33593,69 +24830,39 @@ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==" }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, "bintrees": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==" }, - "bowser": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" - }, - "brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "requires": { - "fill-range": "^7.1.1" - } - }, - "browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", - "dev": true, + "boring-name-generator": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/boring-name-generator/-/boring-name-generator-1.0.3.tgz", + "integrity": "sha512-1wEo1pNahY9js7Vkp1RQa/VWdWrXYJnVAmsHV3Pw/0YzspjABLw7dcekjukOMTIYWr8ir/aG0GX1eoEkYhpnUg==", "requires": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "commander": "^6.1.0", + "lodash": "^4.17.20" + }, + "dependencies": { + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==" + } } }, - "bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } + "bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==" }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "node-int64": "^0.4.0" + "fill-range": "^7.1.1" } }, "buffer": { @@ -33673,6 +24880,18 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + } + }, "call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -33682,22 +24901,20 @@ "function-bind": "^1.1.2" } }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + } }, - "caniuse-lite": { - "version": "1.0.30001715", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz", - "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==", + "chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true }, "chalk": { @@ -33718,50 +24935,29 @@ "requires": { "color-convert": "^2.0.1" } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true } } }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true + "change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==" }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, + "change-case-all": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/change-case-all/-/change-case-all-2.1.0.tgz", + "integrity": "sha512-v6b0WWWkZUMHVuYk82l+WROgkUm4qEN2w5hKRNWtEOYwWqUGoi8C6xH0l1RLF1EoWqDFK6MFclmN3od6ws3/uw==", "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "change-case": "^5.2.0", + "sponge-case": "^2.0.2", + "swap-case": "^3.0.2", + "title-case": "^3.0.3" } }, "ci-info": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz", - "integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true }, "cjs-module-lexer": { @@ -33779,22 +24975,28 @@ "wrap-ansi": "^7.0.0" } }, + "close-with-grace": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/close-with-grace/-/close-with-grace-2.5.0.tgz", + "integrity": "sha512-MewUtZQU6N4YVHIne63zGtjIQzTINgr6lQp2Y0CutaCw2FsdYahW57dH1Wdz+aV5ipbBzEBZD5znwX2NooS+IA==" + }, "cluster-key-slot": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } }, - "collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "colorette": { "version": "2.0.19", @@ -33833,24 +25035,10 @@ "dot-prop": "^5.1.0" } }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "connection-string": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/connection-string/-/connection-string-4.3.6.tgz", - "integrity": "sha512-dwaq4BMeiIIWry/oQxjstPB7Xp0K1nGjaY8p6PHQB+J9ZJuIvNp7ux3Noupq0hMd/dqEHXkfdmmGFOKTbGKWmw==" - }, "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==" }, "conventional-changelog-conventionalcommits": { "version": "5.0.0", @@ -33869,45 +25057,24 @@ "dev": true }, "cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==" }, "cookie-es": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-2.0.0.tgz", "integrity": "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==" }, - "core-js-compat": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz", - "integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==", - "dev": true, - "requires": { - "browserslist": "^4.24.4" - } - }, - "create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, + "cron": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-4.4.0.tgz", + "integrity": "sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==", "requires": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" + "@types/luxon": "~3.7.0", + "luxon": "~3.7.0" } }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, "cron-parser": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", @@ -33926,22 +25093,26 @@ "which": "^2.0.1" } }, - "crypto-js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" - }, "custom-error-instance": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz", "integrity": "sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg==", "dev": true }, + "data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" + }, "dateformat": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "dev": true + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==" + }, + "debounce": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz", + "integrity": "sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==" }, "debug": { "version": "4.3.4", @@ -33951,24 +25122,16 @@ "ms": "2.1.2" } }, - "dedent": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", - "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, - "requires": {} - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } }, "delayed-stream": { "version": "1.0.0", @@ -33990,33 +25153,12 @@ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, "dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -34041,51 +25183,16 @@ } }, "duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", "requires": { "end-of-stream": "^1.4.1", "inherits": "^2.0.3", "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - }, - "dynamic-dedupe": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", - "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", - "dev": true, - "requires": { - "xtend": "^4.0.0" - } - }, - "eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" - }, - "ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "requires": { - "jake": "^10.8.5" + "stream-shift": "^1.0.2" } }, - "electron-to-chromium": { - "version": "1.5.140", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.140.tgz", - "integrity": "sha512-o82Rj+ONp4Ip7Cl1r7lrqx/pXhbp/lh9DpKcMNscFJdh8ebyRofnc7Sh01B4jx403RI0oqTBvlZ7OBIZLMr2+Q==", - "dev": true - }, - "emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true - }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -34099,15 +25206,6 @@ "once": "^1.4.0" } }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, "es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -34118,6 +25216,12 @@ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" }, + "es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true + }, "es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -34181,205 +25285,20 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - }, - "eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - } - } - }, - "eslint-config-prettier": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", - "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", - "dev": true, - "requires": {} - }, - "eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true - }, "esm": { "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" }, - "espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "requires": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "requires": { - "estraverse": "^5.2.0" + "@types/estree": "^1.0.0" } }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, "event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -34390,52 +25309,29 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, + "events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "bare-events": "^2.7.0" } }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true }, - "expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - } - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "fast-copy": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-2.1.3.tgz", - "integrity": "sha512-LDzYKNTHhD+XOp8wGMuCkY4eTxFZOOycmpwLBiuF3r3OjOmZnURRD8t2dUAbmKuXGbo/MGggwbSjcBdp8QT0+g==", - "dev": true + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.2.tgz", + "integrity": "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==" }, "fast-decode-uri-component": { "version": "1.0.1", @@ -34447,11 +25343,10 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true + "fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" }, "fast-glob": { "version": "3.3.2", @@ -34471,31 +25366,24 @@ "resolved": "https://registry.npmjs.org/fast-json-parse/-/fast-json-parse-1.0.3.tgz", "integrity": "sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw==" }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==" }, "fast-json-stringify": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.0.1.tgz", - "integrity": "sha512-s7SJE83QKBZwg54dIbD5rCtzOBVD43V1ReWXXYqBgwCwHLYAAT0RQc/FmrQglXqWPpz6omtryJQOau5jI4Nrvg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.3.0.tgz", + "integrity": "sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA==", "requires": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", - "json-schema-ref-resolver": "^2.0.0", + "json-schema-ref-resolver": "^3.0.0", "rfdc": "^1.2.0" } }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, "fast-querystring": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", @@ -34504,36 +25392,45 @@ "fast-decode-uri-component": "^1.0.1" } }, - "fast-redact": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.1.tgz", - "integrity": "sha512-odVmjC8x8jNeMZ3C+rPMESzXVSEU8tSWSHv9HFxP2mm89G/1WwqhrerJDQm9Zus8X6aoRgQDThKqptdNA6bt+A==" - }, "fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==" }, + "fast-xml-builder": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "requires": { + "path-expression-matcher": "^1.1.3" + } + }, "fast-xml-parser": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", - "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "version": "5.5.8", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.8.tgz", + "integrity": "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==", "requires": { - "strnum": "^1.0.5" + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.2.0", + "strnum": "^2.2.0" } }, + "fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==" + }, "fastify": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.6.0.tgz", - "integrity": "sha512-9j2r9TnwNsfGiCKGYT0Voqy244qwcoYM9qvNi/i+F8sNNWDnqUEVuGYNc9GyjldhXmMlJmVPS6gI1LdvjYGRJw==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.8.3.tgz", + "integrity": "sha512-XJXpRQ41+rsJ/GLeP9vyDC+fBXilcTlEXokMSexkdEkla4uf7ZQNaI5xl3el+kW5TZQulqYxLr659ey/KX7XmQ==", "requires": { - "@fastify/ajv-compiler": "^4.0.0", + "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", @@ -34542,104 +25439,42 @@ "fast-json-stringify": "^6.0.0", "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", - "pino": "^9.0.0", + "pino": "^9.14.0 || ^10.1.0", "process-warning": "^5.0.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" - }, - "dependencies": { - "secure-json-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.0.0.tgz", - "integrity": "sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==" - } - } - }, - "fastify-metrics": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/fastify-metrics/-/fastify-metrics-12.1.0.tgz", - "integrity": "sha512-EpbT+W1jm8kMbkCPvfW4j23y3BZlXGOcO6+75EFTKDxbJIyXbldrFIoVoP0oD4CsqrKeIARvrOjbZNqK5MdRwQ==", - "requires": { - "fastify-plugin": "^5.0.0", - "prom-client": "^15.1.3" } }, "fastify-plugin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.0.1.tgz", - "integrity": "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==" - }, - "fastify-xml-body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/fastify-xml-body-parser/-/fastify-xml-body-parser-2.2.0.tgz", - "integrity": "sha512-Jxltec0Iin4QX+DEQoYCyGmU5cTRtI0x22mRT/3FBQMhTEn7KNTHnnEtbyN3+6SLgW8cSirnOe1t8vqn77vR+Q==", - "requires": { - "fast-xml-parser": "^4.1.2", - "fastify-plugin": "^3.0.0" - }, - "dependencies": { - "fastify-plugin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.1.tgz", - "integrity": "sha512-qKcDXmuZadJqdTm6vlCqioEbyewF60b/0LOFCcYN1B6BIZGlYJumWWOYs70SFYLDAH4YqdE1cxH/RKMG7rFxgA==" - } - } + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.1.0.tgz", + "integrity": "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==" }, "fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "requires": { "reusify": "^1.0.4" } }, - "fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "requires": { - "flat-cache": "^3.0.4" + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" } }, - "filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, + "figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "requires": { - "minimatch": "^5.0.1" - }, - "dependencies": { - "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, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } + "is-unicode-supported": "^2.0.0" } }, "fill-range": { @@ -34652,66 +25487,33 @@ } }, "find-my-way": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.3.0.tgz", - "integrity": "sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.5.0.tgz", + "integrity": "sha512-VW2RfnmscZO5KgBY5XVyKREMW5nMZcxDy+buTOsL+zIPnBlbKm+00sgzoQzq1EVh4aALZLfKdwv6atBGcjvjrQ==", "requires": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", "safe-regex2": "^5.0.0" } }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", "dev": true, "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" + "micromatch": "^4.0.2" } }, - "flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", - "dev": true - }, "follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" - }, - "foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "dependencies": { - "signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" - } - } + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==" }, "form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -34720,10 +25522,24 @@ "mime-types": "^2.1.12" } }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "requires": { + "fetch-blob": "^3.1.2" + } + }, + "forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==" + }, "fs-extra": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "dev": true, "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -34735,12 +25551,6 @@ "resolved": "https://registry.npmjs.org/fs-xattr/-/fs-xattr-0.3.1.tgz", "integrity": "sha512-UVqkrEW0GfDabw4C3HOrFlxKfx0eeigfRne69FxSBdHIP8Qt5Sq6Pu3RM9KmMlkygtC4pPKkj5CiPO5USnj2GA==" }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, "fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -34754,23 +25564,34 @@ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "gaxios": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", - "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", "requires": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9", - "uuid": "^9.0.1" + "node-fetch": "^3.3.2" + }, + "dependencies": { + "node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } + } } }, "gcp-metadata": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", - "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", "requires": { - "gaxios": "^6.0.0", + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, @@ -34780,12 +25601,6 @@ "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", "optional": true }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -34822,12 +25637,6 @@ "es-object-atoms": "^1.0.0" } }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, "get-tsconfig": { "version": "4.7.5", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", @@ -34842,33 +25651,40 @@ "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==" }, + "gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==" + }, "glob": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", - "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "dependencies": { + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" + }, "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "requires": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" } }, "minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "requires": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" } } } @@ -34882,14 +25698,10 @@ "is-glob": "^4.0.1" } }, - "globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } + "google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==" }, "gopd": { "version": "1.2.0", @@ -34897,22 +25709,31 @@ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, "graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" - }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "graphql": { + "version": "16.13.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", + "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==" + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0" + } + }, "has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -34934,6 +25755,16 @@ "function-bind": "^1.1.2" } }, + "help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + }, + "hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==" + }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -34941,32 +25772,26 @@ "dev": true }, "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" } }, "https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "requires": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" } }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, "humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -34975,62 +25800,37 @@ "ms": "^2.0.0" } }, + "hyperid": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/hyperid/-/hyperid-3.3.0.tgz", + "integrity": "sha512-7qhCVT4MJIoEsNcbhglhdmBKb09QtcmJNiIQGq7js/Khf5FtQQ9bzcAuloeqBeee7XD7JqDeve9KNlQya5tSGQ==", + "requires": { + "buffer": "^5.2.1", + "uuid": "^8.3.2", + "uuid-parse": "^1.1.0" + } + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, - "ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, "import-in-the-middle": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.11.0.tgz", - "integrity": "sha512-5DimNQGoe0pLUHbR9qK84iWaWjjbsxiqXnw6Qz64+azRgleqv9k2kTt5fw7QsOpmaGYtuxxursnPPsnTKEx10Q==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", + "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", "requires": { - "acorn": "^8.8.2", + "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", - "cjs-module-lexer": "^1.2.2", - "module-details-from-path": "^1.0.3" - } - }, - "import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + }, + "dependencies": { + "cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==" + } } }, "inherits": { @@ -35060,29 +25860,14 @@ } }, "ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==" + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==" }, "ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==" }, "is-core-module": { "version": "2.13.1", @@ -35092,6 +25877,12 @@ "hasown": "^2.0.0" } }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -35103,12 +25894,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -35129,11 +25914,10 @@ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true + "is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" }, "is-retry-allowed": { "version": "2.2.0", @@ -35142,574 +25926,77 @@ }, "is-stream": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jackspeak": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", - "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", - "requires": { - "@isaacs/cliui": "^8.0.2", - "@pkgjs/parseargs": "^0.11.0" - } - }, - "jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "dev": true, - "requires": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - } - }, - "jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "requires": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - } - }, - "jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - } - }, - "jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "requires": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - } - }, - "jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - } - }, - "jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - } - }, - "jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - } - }, - "jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true - }, - "jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "requires": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - } - }, - "jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - } - }, - "jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - } - }, - "jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, - "jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "requires": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - } + "is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==" }, - "jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, "requires": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } + "is-docker": "^2.0.0" } }, - "jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, - "jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - } + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, - "jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } + "isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "requires": {} }, - "jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "dependencies": { - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - } - } + "istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true }, - "jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "requires": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" } }, - "jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "requires": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" } }, "jose": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz", - "integrity": "sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw==" + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==" }, "joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "dev": true + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==" }, "js-base64": { "version": "3.7.5", @@ -35717,26 +26004,18 @@ "integrity": "sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==", "dev": true }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "requires": { "argparse": "^2.0.1" } }, - "jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true + "jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==" }, "json-bigint": { "version": "1.0.0", @@ -35746,16 +26025,10 @@ "bignumber.js": "^9.0.0" } }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, "json-schema-ref-resolver": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-2.0.1.tgz", - "integrity": "sha512-HG0SIB9X4J8bwbxCbnd5FfPEbcXAJYTi1pBJeP/QPON+w8ovSME8iRG+ElHNxZNX2Qh6eYn1GdzJFS4cDFfx0Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", "requires": { "dequal": "^2.0.3" } @@ -35786,33 +26059,59 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true + "json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + } }, "json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, "requires": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" } }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", "dev": true }, + "jsonpath-plus": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz", + "integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==", + "requires": { + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" + } + }, + "klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11" + } + }, "knex": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/knex/-/knex-3.1.0.tgz", @@ -35832,30 +26131,12 @@ "resolve-from": "^5.0.0", "tarn": "^3.0.2", "tildify": "2.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" - } } }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" }, "light-my-request": { "version": "6.6.0", @@ -35874,25 +26155,107 @@ } } }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "detect-libc": "^2.0.3", + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" } }, + "lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "dev": true, + "optional": true + }, + "lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "dev": true, + "optional": true + }, + "lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "dev": true, + "optional": true + }, + "lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "dev": true, + "optional": true + }, + "lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "dev": true, + "optional": true + }, + "lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "dev": true, + "optional": true + }, + "lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "dev": true, + "optional": true + }, + "lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "dev": true, + "optional": true + }, + "lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "dev": true, + "optional": true + }, + "lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "dev": true, + "optional": true + }, + "lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "dev": true, + "optional": true + }, "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" }, "lodash._baseiteratee": { "version": "4.7.0", @@ -35945,12 +26308,6 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -35961,23 +26318,16 @@ "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, "lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==" + }, "lodash.uniqby": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz", @@ -35994,42 +26344,47 @@ "integrity": "sha512-9q61GYsRSf4vZHXRvBClamFvPSRvfgWkDaBkYg4Q4DuofcpwikxPROB/h8rVX3xyE8jrKqKM0mAlrqCzJQS3MQ==" }, "long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" }, "lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==" + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==" }, "luxon": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", - "integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==" + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==" }, - "make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "requires": { - "semver": "^7.5.3" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "requires": { - "tmpl": "1.0.5" + "semver": "^7.5.3" } }, "math-intrinsics": { @@ -36037,17 +26392,6 @@ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" }, - "md5-file": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-5.0.0.tgz", - "integrity": "sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw==" - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -36082,42 +26426,20 @@ "mime-db": "1.52.0" } }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, "minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==" }, "module-details-from-path": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", - "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==" }, "ms": { "version": "2.1.2", @@ -36133,16 +26455,10 @@ "readable-stream": "^3.6.0" } }, - "mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true }, "negotiator": { @@ -36150,6 +26466,11 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, "node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -36158,33 +26479,22 @@ "whatwg-url": "^5.0.0" } }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true + "node-gyp-build": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.9.0.tgz", + "integrity": "sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A==" }, - "node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true + "oauth4webapi": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.1.tgz", + "integrity": "sha512-olkZDELNycOWQf9LrsELFq8n05LwJgV8UkrS0cburk6FOwf8GvLam+YB+Uj5Qvryee+vwWOfQVeI5Vm0MVg7SA==" }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, "object-sizeof": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/object-sizeof/-/object-sizeof-2.6.4.tgz", @@ -36204,6 +26514,12 @@ } } }, + "obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true + }, "on-exit-leak-free": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", @@ -36217,13 +26533,14 @@ "wrappy": "1" } }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "dev": true, "requires": { - "mimic-fn": "^2.1.0" + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" } }, "openapi-types": { @@ -36231,92 +26548,196 @@ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==" }, - "optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "openid-client": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.0.tgz", + "integrity": "sha512-oG1d1nAVhIIE+JSjLS+7E9wY1QOJpZltkzlJdbZ7kEn7Hp3hqur2TEeQ8gLOHoHkhbRAGZJKoOnEQcLOQJuIyg==", "requires": { - "yocto-queue": "^0.1.0" + "jose": "^6.1.0", + "oauth4webapi": "^3.8.1" } }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, + "otlp-logger": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/otlp-logger/-/otlp-logger-1.1.13.tgz", + "integrity": "sha512-r53tPnMLprtQSMOJUkj4Az4tR8NL+U+8C7M8BV1ZA9y7cDfAbWQp2mRL/eYS/O786oAi9KnN9hKsZ5cFKNchKw==", "requires": { - "p-limit": "^2.2.0" + "@opentelemetry/api-logs": "^0.206.0", + "@opentelemetry/exporter-logs-otlp-grpc": "^0.206.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.206.0", + "@opentelemetry/exporter-logs-otlp-proto": "^0.206.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/sdk-logs": "^0.206.0" }, "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, + "@opentelemetry/api-logs": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.206.0.tgz", + "integrity": "sha512-yIVDu9jX//nV5wSMLZLdHdb1SKHIMj9k+wQVFtln5Flcgdldz9BkHtavvExQiJqBZg2OpEEJEZmzQazYztdz2A==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", + "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", + "requires": { + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.206.0.tgz", + "integrity": "sha512-kJKxKBaGwqWop95d6tcluz260IWwIgOG0BH8oVm6429tg8LxY2PJb7Om8d5s+5vOFM8DkUYCnIpn9d/13/RcKQ==", + "requires": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.206.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.206.0", + "@opentelemetry/otlp-transformer": "0.206.0", + "@opentelemetry/sdk-logs": "0.206.0" + } + }, + "@opentelemetry/exporter-logs-otlp-http": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.206.0.tgz", + "integrity": "sha512-VWcHEnS+1kN+sQTAdCgSn2anqHPxY1/e52fhpe2mcSnEaXI1srFf3RU5DAu7hzQO6T9DPQzOKG8kc76vCtyYDw==", + "requires": { + "@opentelemetry/api-logs": "0.206.0", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.206.0", + "@opentelemetry/otlp-transformer": "0.206.0", + "@opentelemetry/sdk-logs": "0.206.0" + } + }, + "@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.206.0.tgz", + "integrity": "sha512-CsYNXJwkn1qCXJGE+/JvvYucAjL8rpaxa2hnl+tDP6M5E0O3UVa8zG4ZUEebjr5J5Nc32egvslEZx5rgNOp3lQ==", + "requires": { + "@opentelemetry/api-logs": "0.206.0", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.206.0", + "@opentelemetry/otlp-transformer": "0.206.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/sdk-logs": "0.206.0", + "@opentelemetry/sdk-trace-base": "2.1.0" + } + }, + "@opentelemetry/otlp-exporter-base": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.206.0.tgz", + "integrity": "sha512-Rv54oSNKMHYS5hv+H5EGksfBUtvPQWFTK+Dk6MjJun9tOijCsFJrhRFvAqg5d67TWSMn+ZQYRKIeXh5oLVrpAQ==", + "requires": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-transformer": "0.206.0" + } + }, + "@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.206.0.tgz", + "integrity": "sha512-IA8EDbrB8OKtidMqErBY8sUc9mh03LOXuNPwp4/rdPrxSt45g1gBuZMovRXdEWfRyKKbF2E7MdipT2m11bs6SQ==", + "requires": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/otlp-exporter-base": "0.206.0", + "@opentelemetry/otlp-transformer": "0.206.0" + } + }, + "@opentelemetry/otlp-transformer": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.206.0.tgz", + "integrity": "sha512-Li2Cik1WnmNbU2mmTnw7DxvRiXhMcnAuTfAclP8y/zy7h5+GrLDpTZ+Z0XUs+Q3MLkb/h3ry4uFrC/z+2a6X7g==", + "requires": { + "@opentelemetry/api-logs": "0.206.0", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/sdk-logs": "0.206.0", + "@opentelemetry/sdk-metrics": "2.1.0", + "@opentelemetry/sdk-trace-base": "2.1.0", + "protobufjs": "^7.3.0" + } + }, + "@opentelemetry/resources": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz", + "integrity": "sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==", + "requires": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + } + }, + "@opentelemetry/sdk-logs": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.206.0.tgz", + "integrity": "sha512-SQ2yTmqe4Mw9RI3a/glVkfjWPsXh6LySvnljXubiZq4zu+UP8NMJt2j82ZsYb+KpD7Eu+/41/7qlJnjdeVjz7Q==", + "requires": { + "@opentelemetry/api-logs": "0.206.0", + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0" + } + }, + "@opentelemetry/sdk-metrics": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.1.0.tgz", + "integrity": "sha512-J9QX459mzqHLL9Y6FZ4wQPRZG4TOpMCyPOh6mkr/humxE1W2S3Bvf4i75yiMW9uyed2Kf5rxmLhTm/UK8vNkAw==", "requires": { - "p-try": "^2.0.0" + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0" + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz", + "integrity": "sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==", + "requires": { + "@opentelemetry/core": "2.1.0", + "@opentelemetry/resources": "2.1.0", + "@opentelemetry/semantic-conventions": "^1.29.0" } } } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } + "parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==" }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "patch-package": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", + "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^10.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.2.4", + "yaml": "^2.2.2" + }, + "dependencies": { + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } } }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "path-expression-matcher": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.1.tgz", + "integrity": "sha512-d7gQQmLvAKXKXE2GeP9apIGbMYKz88zWdsn/BN2HRWVQsDFdUY36WSLTY0Jvd4HWi7Fb30gQ62oAOzdgJA6fZw==" }, "path-key": { "version": "3.1.1", @@ -36329,43 +26750,42 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "requires": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" - }, - "dependencies": { - "lru-cache": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", - "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==" - } } }, + "pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, "pg": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.0.tgz", - "integrity": "sha512-7SKfdvP8CTNXjMUzfcVTaI+TDzBEeaUnVwiVGZQD1Hh33Kpev7liQba9uLd4CfN8r9mCVsD0JIpq03+Unpz+kg==", - "requires": { - "pg-cloudflare": "^1.2.5", - "pg-connection-string": "^2.9.0", - "pg-pool": "^3.10.0", - "pg-protocol": "^1.10.0", + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "requires": { + "pg-cloudflare": "^1.2.7", + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "dependencies": { "pg-connection-string": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.0.tgz", - "integrity": "sha512-P2DEBKuvh5RClafLngkAuGe9OUlFV7ebu8w1kmaaOgPcpJd1RIFh7otETfI6hAR8YupOLFTY7nuvvIn7PLciUQ==" + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==" } } }, "pg-boss": { - "version": "git+ssh://git@github.com/supabase/pg-boss.git#ca012f295507400c1a9ada9dbf0f4d11cdbe3a2f", + "version": "git+ssh://git@github.com/supabase/pg-boss.git#89b675b057ff7cec3ca2c434e4d3005c35a76bd9", "from": "pg-boss@github:supabase/pg-boss#feat/exactly_once", "requires": { "cron-parser": "^4.9.0", @@ -36374,9 +26794,9 @@ } }, "pg-cloudflare": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.5.tgz", - "integrity": "sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", "optional": true }, "pg-connection-string": { @@ -36405,15 +26825,15 @@ } }, "pg-pool": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.0.tgz", - "integrity": "sha512-DzZ26On4sQ0KmqnO34muPcmKbhrjmyiO4lCCR0VwEd7MjmiKf5NTg/6+apUEu0NF7ESa37CGzFxH513CoUmWnA==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", "requires": {} }, "pg-protocol": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.0.tgz", - "integrity": "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q==" + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==" }, "pg-types": { "version": "2.2.0", @@ -36442,44 +26862,36 @@ "dev": true }, "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true }, "pino": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.7.0.tgz", - "integrity": "sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", "requires": { + "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", + "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", - "thread-stream": "^3.0.0" + "thread-stream": "^4.0.0" }, "dependencies": { "pino-abstract-transport": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", - "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", "requires": { "split2": "^4.0.0" } - }, - "sonic-boom": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", - "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", - "requires": { - "atomic-sleep": "^1.0.0" - } } } }, @@ -36532,96 +26944,57 @@ } } }, + "pino-opentelemetry-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-opentelemetry-transport/-/pino-opentelemetry-transport-2.0.0.tgz", + "integrity": "sha512-tWoq02WEtnCWfr63Co1n0sGlDpkBz2YUovSAsSBv/+jwKUIn/PjxwRileGViKrH/K3e+oc71nGl6yUdgQlrVkg==", + "requires": { + "otlp-logger": "^1.1.4", + "pino-abstract-transport": "^3.0.0" + }, + "dependencies": { + "pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "requires": { + "split2": "^4.0.0" + } + } + } + }, "pino-pretty": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-8.1.0.tgz", - "integrity": "sha512-oKfI8qKXR2a3haHs/X8iB6QSnWLqoOGAjwxIAXem4+XOGIGNw7IKpozId1uE7j89Rj46HIfWnGbAgmQmr8+yRw==", - "dev": true, + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz", + "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", "requires": { "colorette": "^2.0.7", "dateformat": "^4.6.3", - "fast-copy": "^2.1.1", + "fast-copy": "^4.0.0", "fast-safe-stringify": "^2.1.1", - "help-me": "^4.0.1", + "help-me": "^5.0.0", "joycon": "^3.1.1", "minimist": "^1.2.6", - "on-exit-leak-free": "^1.0.0", - "pino-abstract-transport": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", "pump": "^3.0.0", - "readable-stream": "^4.0.0", - "secure-json-parse": "^2.4.0", - "sonic-boom": "^3.0.0", - "strip-json-comments": "^3.1.1" + "secure-json-parse": "^4.0.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^5.0.2" }, "dependencies": { - "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, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "help-me": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.0.1.tgz", - "integrity": "sha512-PLv01Z+OhEPKj2QPYB4kjoCUkopYNPUK3EROlaPIf5bib752fZ+VCvGDAoA+FXo/OwCyLEA4D2e0mX8+Zhcplw==", - "dev": true, - "requires": { - "glob": "^8.0.0", - "readable-stream": "^3.6.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dev": true, + "pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", "requires": { - "brace-expansion": "^2.0.1" + "split2": "^4.0.0" } }, - "on-exit-leak-free": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-1.0.0.tgz", - "integrity": "sha512-Ve8ubhrXRdnuCJ5bQSQpP3uaV43K1PMcOfSRC1pqHgRZommXCgsXwh08jVC5NpjwScE23BPDwDvVg4cov3mwjw==", - "dev": true - }, - "readable-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.1.0.tgz", - "integrity": "sha512-sVisi3+P2lJ2t0BPbpK629j8wRW06yKGJUcaLAGXPAUhyUxVJm7VsCTit1PFgT4JHUDMrGNR+ZjSKpzGaRF3zw==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0" - } + "strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==" } } }, @@ -36630,19 +27003,24 @@ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" }, - "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true + "platformatic": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/platformatic/-/platformatic-3.52.0.tgz", + "integrity": "sha512-GqWwRgtgIudyELh0QhZ9yf602vBgmrv7pOITskVIPALE9dLfZzTCWq9b+d8vVsAEaBRVMlxx7I8m4be+AP9yzw==", + "requires": { + "@platformatic/foundation": "3.52.0", + "wattpm": "3.52.0" + } }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "postcss": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", "dev": true, "requires": { - "find-up": "^4.0.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" } }, "postgres-array": { @@ -36677,11 +27055,18 @@ "sql-template-strings": "^2.2.2" } }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true + "pprof-format": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/pprof-format/-/pprof-format-2.2.1.tgz", + "integrity": "sha512-p4tVN7iK19ccDqQv8heyobzUmbHyds4N2FI6aBMcXz6y99MglTWDxIyhFkNaLeEXs6IFUEzT0zya0icbSLLY0g==" + }, + "pprof-to-md": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/pprof-to-md/-/pprof-to-md-0.1.0.tgz", + "integrity": "sha512-2OnF8vTBT37lfaDwIK9xJHGl6Sl05mQpoFjnqLOrLw0XTSN8tj6nVlwIOSAi67Ys57/qZpyHvqzpQlw36bH4kg==", + "requires": { + "pprof-format": "^2.1.0" + } }, "prettier": { "version": "2.8.8", @@ -36689,24 +27074,12 @@ "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "parse-ms": "^4.0.0" } }, "process-warning": { @@ -36723,16 +27096,6 @@ "tdigest": "^0.1.1" } }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, "proper-lockfile": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", @@ -36753,9 +27116,9 @@ } }, "protobufjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -36795,17 +27158,6 @@ "pump": "^3.0.0" } }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true - }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -36828,11 +27180,30 @@ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true + "react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==" + }, + "react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "requires": { + "scheduler": "^0.27.0" + } + }, + "react-pprof": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/react-pprof/-/react-pprof-1.5.1.tgz", + "integrity": "sha512-IOdUPcVjBMahl9Uw+TnE2CKybfVjiNd1mKjHMipJInezH8XdDRL5b67G5wzJkrii0L/Xj+LmESMm5d1gPIgv7Q==", + "requires": { + "gl-matrix": "^3.4.3", + "pprof-format": "^2.2.1", + "protobufjs": "^7.5.3", + "react": "^19.1.0", + "react-dom": "^19.1.0" + } }, "readable-stream": { "version": "3.6.0", @@ -36844,15 +27215,6 @@ "util-deprecate": "^1.0.1" } }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, "real-require": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", @@ -36879,72 +27241,11 @@ "redis-errors": "^1.0.0" } }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", - "dev": true, - "requires": { - "regenerate": "^1.4.2" - } - }, "regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, - "regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", - "dev": true, - "requires": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", - "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - } - }, - "regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true - }, - "regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", - "dev": true, - "requires": { - "jsesc": "~3.0.2" - }, - "dependencies": { - "jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true - } - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -36956,19 +27257,18 @@ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, "require-in-the-middle": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.4.0.tgz", - "integrity": "sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", "requires": { "debug": "^4.3.5", - "module-details-from-path": "^1.0.3", - "resolve": "^1.22.8" + "module-details-from-path": "^1.0.3" }, "dependencies": { "debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "requires": { "ms": "^2.1.3" } @@ -36996,28 +27296,10 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" }, "resolve-pkg-maps": { "version": "1.0.0", @@ -37044,12 +27326,6 @@ } } }, - "resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true - }, "ret": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", @@ -37065,34 +27341,39 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, + "rfc4648": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.4.tgz", + "integrity": "sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==" + }, "rfdc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } + "rolldown": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", + "dev": true, + "requires": { + "@oxc-project/types": "=0.124.0", + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15", + "@rolldown/pluginutils": "1.0.0-rc.15" } }, "run-parallel": { @@ -37127,16 +27408,30 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, + "scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" + }, "secure-json-parse": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==" + }, + "semgrator": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/semgrator/-/semgrator-0.3.0.tgz", + "integrity": "sha512-TIMBco3kY4+jNk+uiSpbW6dwZ2kCnLPEcPbxIpcDV9UcVL0egYsiQIhljZU5meLTYjNRqFyZ+JwdsfC4ryrUCA==", + "requires": { + "abstract-logging": "^2.0.1", + "rfdc": "^1.3.1", + "semver": "^7.6.0" + } }, "semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==" }, "serialize-error": { "version": "8.1.0", @@ -37151,6 +27446,20 @@ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -37169,10 +27478,11 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, - "shimmer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true }, "signal-exit": { "version": "3.0.7", @@ -37180,53 +27490,73 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + } + } }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" }, - "sonic-boom": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.2.0.tgz", - "integrity": "sha512-SbbZ+Kqj/XIunvIAgUZRlqd6CGQYq71tRRbXR92Za8J/R3Yh4Av+TWENiSiEgnlwckYLyP0YZQWVfyNC0dzLaA==", - "dev": true, + "socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "requires": { - "atomic-sleep": "^1.0.0" + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "requires": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + } }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, + "sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "atomic-sleep": "^1.0.0" } }, + "source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true + }, "split2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", - "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "sponge-case": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sponge-case/-/sponge-case-2.0.3.tgz", + "integrity": "sha512-i4h9ZGRfxV6Xw3mpZSFOfbXjf0cQcYmssGWutgNIfFZ2VM+YIWfD71N/kjjwK6X/AAHzBr+rciEcn/L34S8TGw==" }, "sql-template-strings": { "version": "2.2.2", @@ -37241,14 +27571,11 @@ "cookie-es": "^2.0.0" } }, - "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - } + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true }, "standard-as-callback": { "version": "2.1.0", @@ -37256,9 +27583,15 @@ "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" }, "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" + }, + "std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true }, "stream-browserify": { "version": "3.0.0", @@ -37272,13 +27605,22 @@ "stream-buffers": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", - "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==", - "dev": true + "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==" }, "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==" + }, + "streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "requires": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } }, "string_decoder": { "version": "1.3.0", @@ -37288,16 +27630,6 @@ "safe-buffer": "~5.2.0" } }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -37308,16 +27640,6 @@ "strip-ansi": "^6.0.1" } }, - "string-width-cjs": { - "version": "npm:string-width@4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -37326,36 +27648,10 @@ "ansi-regex": "^5.0.1" } }, - "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "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 - }, "strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz", + "integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==" }, "supports-color": { "version": "7.2.0", @@ -37371,6 +27667,49 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "swap-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/swap-case/-/swap-case-3.0.3.tgz", + "integrity": "sha512-6p4op8wE9CQv7uDFzulI6YXUw4lD9n4oQierdbFThEKVWVQcbQcUjdP27W8XE7V4QnWmnq9jueSHceyyQnqQVA==" + }, + "systeminformation": { + "version": "5.31.2", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.2.tgz", + "integrity": "sha512-ietGQGFhhZNBPgNv9vljgT8gzbYgQr6t0yGAo0Vdb5Jyilb574Vp+AuX2Or9rpBq3ho4mJRawLIUa9+CiILJdg==" + }, + "table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + } + }, + "tar-fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "requires": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "requires": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "tarn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", @@ -37384,43 +27723,18 @@ "bintrees": "1.0.2" } }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, + "text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } + "b4a": "^1.6.4" } }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, "thread-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", - "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", + "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", "requires": { "real-require": "^0.2.0" } @@ -37439,10 +27753,61 @@ "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==" }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "tinyexec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", + "dev": true + }, + "tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "requires": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "dependencies": { + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + }, + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true + } + } + }, + "tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true + }, + "title-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", + "integrity": "sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==", + "requires": { + "tslib": "^2.0.3" + } + }, + "tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "dev": true }, "to-regex-range": { @@ -37469,134 +27834,12 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, - "tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true - }, "ts-algebra": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-1.2.2.tgz", "integrity": "sha512-kloPhf1hq3JbCPOTYoOWDKxebWjNb2o/LKnNfkWhxVVisFFmMJPPdJeGoGmM+iRLyoXAR61e08Pb+vUXINg8aA==", "dev": true }, - "ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", - "dev": true, - "requires": {} - }, - "ts-jest": { - "version": "29.3.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.2.tgz", - "integrity": "sha512-bJJkrWc6PjFVz5g2DGCNUo8z7oFEYaz1xP1NpeDU7KNLMWPpEyV8Chbpkn8xjzgRDpQhnGMyvyldoL7h8JXyug==", - "dev": true, - "requires": { - "bs-logger": "^0.2.6", - "ejs": "^3.1.10", - "fast-json-stable-stringify": "^2.1.0", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.1", - "type-fest": "^4.39.1", - "yargs-parser": "^21.1.1" - }, - "dependencies": { - "type-fest": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz", - "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==", - "dev": true - } - } - }, - "ts-node-dev": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.1.8.tgz", - "integrity": "sha512-Q/m3vEwzYwLZKmV6/0VlFxcZzVV/xcgOt+Tx/VjaaRHyiBcFlV0541yrT09QjzzCxlDZ34OzKjrFAynlmtflEg==", - "dev": true, - "requires": { - "chokidar": "^3.5.1", - "dynamic-dedupe": "^0.3.0", - "minimist": "^1.2.5", - "mkdirp": "^1.0.4", - "resolve": "^1.0.0", - "rimraf": "^2.6.1", - "source-map-support": "^0.5.12", - "tree-kill": "^1.2.2", - "ts-node": "^9.0.0", - "tsconfig": "^7.0.0" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - } - } - } - }, - "tsconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", - "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", - "dev": true, - "requires": { - "@types/strip-bom": "^3.0.0", - "@types/strip-json-comments": "0.0.30", - "strip-bom": "^3.0.0", - "strip-json-comments": "^2.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - } - } - }, "tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", @@ -37628,21 +27871,6 @@ "url-parse": "^1.5.7" } }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -37654,66 +27882,42 @@ "integrity": "sha512-Tfay0l6gJMP5rkil8CzGbLthukn+9BN/VXWcABVFPjOoelJ+koW8BuPZYk+h/L+lEeIp1fSzVRiWRPIjKVjPdg==" }, "typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true }, - "undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "dev": true + "undici": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", + "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==" }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, + "undici-thread-interceptor": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/undici-thread-interceptor/-/undici-thread-interceptor-1.3.1.tgz", + "integrity": "sha512-s/TUkGeVj+C4Rr3Vsy84Kewj97m2QBNx6IjMGM1nzfoKWqNlcLqtFQCbXQjobRJQKi3u9Hc796/YvJIv7W7qWQ==", "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" + "fastq": "^1.19.1", + "hyperid": "^3.2.0", + "light-my-request": "^6.5.1", + "undici": "^7.0.0" } }, - "unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", - "dev": true + "undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==" }, - "unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true + "unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==" }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" - }, - "update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "requires": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "requires": { - "punycode": "^2.1.0" - } + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true }, "url-parse": { "version": "1.5.10", @@ -37731,30 +27935,103 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, - "v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" + "uuid-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/uuid-parse/-/uuid-parse-1.1.0.tgz", + "integrity": "sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==" + }, + "vitest": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", + "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", + "dev": true, + "requires": { + "@vitest/expect": "4.1.4", + "@vitest/mocker": "4.1.4", + "@vitest/pretty-format": "4.1.4", + "@vitest/runner": "4.1.4", + "@vitest/snapshot": "4.1.4", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "dependencies": { + "@vitest/mocker": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", + "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", + "dev": true, + "requires": { + "@vitest/spy": "4.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + } + }, + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true + }, + "vite": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", + "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", + "dev": true, + "requires": { + "fsevents": "~2.3.3", + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.15", + "tinyglobby": "^0.2.15" + } + } } }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, + "wattpm": { + "version": "3.52.0", + "resolved": "https://registry.npmjs.org/wattpm/-/wattpm-3.52.0.tgz", + "integrity": "sha512-Lm8wkRLxjWkfKEXaAt2B6R5jeZ1GIt4UKCDL9sx+zS+lVd5r600kOvZ93sTFGzGetaD/ENXJtY35r6/YccEWIw==", "requires": { - "makeerror": "1.0.12" + "@fastify/websocket": "^11.0.0", + "@platformatic/control": "3.52.0", + "@platformatic/foundation": "3.52.0", + "@platformatic/runtime": "3.52.0", + "colorette": "^2.0.20", + "pino-pretty": "^13.0.0", + "split2": "^4.2.0", + "table": "^6.8.2" + }, + "dependencies": { + "colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + } } }, + "web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" + }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -37777,47 +28054,18 @@ "isexe": "^2.0.0" } }, - "word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - } + "siginfo": "^2.0.0", + "stackback": "0.0.2" } }, - "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", + "wrap-ansi": { + "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "requires": { @@ -37833,19 +28081,6 @@ "requires": { "color-convert": "^2.0.1" } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" } } }, @@ -37854,15 +28089,11 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } + "ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "requires": {} }, "xml2js": { "version": "0.6.2", @@ -37895,9 +28126,9 @@ "optional": true }, "yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==" + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==" }, "yargs": { "version": "17.7.2", @@ -37918,17 +28149,10 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==" } } } diff --git a/package.json b/package.json index 4d7994aea..23e57d910 100644 --- a/package.json +++ b/package.json @@ -1,130 +1,140 @@ { "name": "supa-storage", - "version": "1.11.2", - "description": "Supabase Storage Service", - "main": "index.js", + "packageManager": "npm@11.12.1", "scripts": { + "postinstall": "patch-package", "dev": "tsx watch src/start/server.ts | pino-pretty", "build": "tsc -noEmit && node ./build.js && resolve-tspaths", "start": "NODE_ENV=production node dist/start/server.js", - "format": "prettier -c --write src/**", - "lint": "prettier -v && prettier -c src/**", + "lint": "biome check . && prettier --check .", + "lint:fix": "biome check --fix . && prettier --write .", "migration:run": "tsx src/scripts/migrate-call.ts", + "migration:test-idempotency": "tsx src/scripts/test-migration-idempotency.ts", "migrations:types": "tsx src/scripts/migrations-types.ts", "docs:export": "tsx ./src/scripts/export-docs.ts", + "pprof:capture": "tsx src/scripts/pprof-client.ts", "test:dummy-data": "tsx -r dotenv/config ./src/test/db/import-dummy-data.ts", - "test": "npm run infra:restart && npm run test:dummy-data && jest --no-cache --runInBand", - "test:coverage": "npm run infra:restart && npm run test:dummy-data && jest --no-cache --runInBand --coverage", - "eslint:check": "eslint 'src/**'", + "test:unit": "vitest run --config vitest.unit.config.ts", + "test:unit:watch": "vitest --config vitest.unit.config.ts", + "test:unit:coverage": "vitest run --coverage --config vitest.unit.config.ts", + "test:integration": "vitest run --config vitest.integration.config.ts", + "test:integration:watch": "vitest --config vitest.integration.config.ts", + "test:integration:coverage": "vitest run --coverage --config vitest.integration.config.ts", + "test": "npm run infra:restart && npm run test:dummy-data && npm run test:integration", + "test:oriole": "npm run infra:restart:oriole && npm run test:dummy-data && npm run test:integration", + "test:coverage": "npm run infra:restart && npm run test:dummy-data && npm run test:integration:coverage", + "test:coverage:ci": "npm run infra:restart:ci && npm run test:dummy-data && npm run test:integration:coverage", "infra:stop": "docker compose --project-directory . -f ./.docker/docker-compose-infra.yml down --remove-orphans", - "infra:start": "docker compose --project-directory . -f ./.docker/docker-compose-infra.yml up -d && sleep 5 && npm run migration:run", - "infra:start:oriole": "docker compose --project-directory . -f ./.docker/docker-compose-infra.yml -f ./.docker/docker-compose-infra-oriole-override.yml up -d && sleep 5 && npm run migration:run", + "infra:start": "docker compose --project-directory . --profile monitoring -f ./.docker/docker-compose-infra.yml up -d && sleep 5 && npm run migration:run", + "infra:start:ci": "docker compose --project-directory . -f ./.docker/docker-compose-infra.yml up -d && sleep 5 && npm run migration:run", + "infra:start:oriole": "docker compose --project-directory . --profile monitoring -f ./.docker/docker-compose-infra.yml -f ./.docker/docker-compose-infra-oriole-override.yml up -d && sleep 5 && npm run migration:run", + "infra:start:ci:oriole": "docker compose --project-directory . -f ./.docker/docker-compose-infra.yml -f ./.docker/docker-compose-infra-oriole-override.yml up -d && sleep 5 && npm run migration:run", "infra:restart": "npm run infra:stop && npm run infra:start", - "infra:restart:oriole": "npm run infra:stop && npm run infra:start:oriole" - }, - "author": "Supabase", - "license": "ISC", - "engines": { - "node": ">= 20.0.0" + "infra:restart:ci": "npm run infra:stop && npm run infra:start:ci", + "infra:restart:oriole": "npm run infra:stop && npm run infra:start:oriole", + "infra:restart:ci:oriole": "npm run infra:stop && npm run infra:start:ci:oriole", + "test:oriole:ci": "npm run infra:restart:ci:oriole && npm run test:dummy-data && npm run test:integration" }, "dependencies": { - "@aws-sdk/client-ecs": "^3.795.0", - "@aws-sdk/client-s3": "3.654.0", - "@aws-sdk/lib-storage": "3.654.0", - "@aws-sdk/s3-request-presigner": "3.654.0", - "@fastify/accepts": "^5.0.2", - "@fastify/multipart": "^9.2.1", + "@aws-sdk/client-ecs": "^3.1023.0", + "@aws-sdk/client-s3": "^3.1023.0", + "@aws-sdk/client-s3vectors": "^3.1023.0", + "@aws-sdk/lib-storage": "^3.1023.0", + "@aws-sdk/s3-request-presigner": "^3.1024.0", + "@fastify/accepts": "^5.0.4", + "@fastify/multipart": "^9.4.0", + "@fastify/otel": "^0.18.1", "@fastify/rate-limit": "^10.3.0", - "@fastify/swagger": "^9.5.1", - "@fastify/swagger-ui": "^5.2.3", + "@fastify/swagger": "^9.7.0", + "@fastify/swagger-ui": "^5.2.5", "@isaacs/ttlcache": "^1.4.1", - "@opentelemetry/api": "^1.8.0", - "@opentelemetry/auto-instrumentations-node": "^0.50.0", - "@opentelemetry/instrumentation-aws-sdk": "^0.44.0", - "@opentelemetry/instrumentation-fastify": "^0.39.0", - "@opentelemetry/instrumentation-http": "^0.53.0", - "@opentelemetry/instrumentation-knex": "^0.40.0", - "@opentelemetry/instrumentation-pg": "^0.44.0", + "@kubernetes/client-node": "^1.3.0", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/auto-instrumentations-node": "^0.70.1", + "@opentelemetry/exporter-metrics-otlp-grpc": "^0.215.0", + "@opentelemetry/exporter-prometheus": "^0.213.0", + "@opentelemetry/host-metrics": "^0.38.3", + "@opentelemetry/instrumentation-aws-sdk": "^0.59.0", + "@opentelemetry/instrumentation-http": "^0.213.0", + "@opentelemetry/instrumentation-knex": "^0.58.0", + "@opentelemetry/instrumentation-pg": "^0.64.0", + "@opentelemetry/instrumentation-runtime-node": "^0.25.0", + "@opentelemetry/sdk-metrics": "^2.6.1", + "@opentelemetry/sdk-node": "^0.213.0", + "@platformatic/control": "^3.52.0", + "@platformatic/flame": "^1.6.0", + "@platformatic/globals": "^3.52.0", + "@platformatic/node": "^3.52.0", + "@platformatic/wattpm-pprof-capture": "^3.52.1", "@shopify/semaphore": "^3.0.2", "@smithy/node-http-handler": "^2.3.1", "@tus/file-store": "2.0.0", - "@tus/s3-store": "2.0.1", + "@tus/s3-store": "2.0.2", "@tus/server": "2.2.1", "agentkeepalive": "^4.5.0", - "ajv": "^8.12.0", + "ajv": "^8.18.0", "async-retry": "^1.3.3", "aws-sigv4-sign": "^1.2.1", - "axios": "^1.9.0", + "axios": "^1.13.6", "axios-retry": "^3.9.1", - "connection-string": "^4.3.6", "conventional-changelog-conventionalcommits": "^5.0.0", - "crypto-js": "^4.2.0", "dotenv": "^16.0.0", - "fastify": "^5.6.0", - "fastify-metrics": "^12.1.0", - "fastify-plugin": "^5.0.1", - "fastify-xml-body-parser": "^2.2.0", - "fs-extra": "^10.0.1", + "fastify": "^5.8.3", + "fastify-plugin": "^5.1.0", "fs-xattr": "0.3.1", - "glob": "^11.0.0", "ioredis": "^5.2.4", - "ip-address": "^10.0.1", - "jose": "^6.0.10", + "ip-address": "^10.1.0", + "jose": "^6.2.2", + "json-bigint": "^1.0.0", "knex": "^3.1.0", - "lru-cache": "^10.2.0", - "md5-file": "^5.0.0", - "multistream": "^4.1.0", + "lru-cache": "^11.2.0", "object-sizeof": "^2.6.4", "pg": "^8.12.0", "pg-boss": "github:supabase/pg-boss#feat/exactly_once", "pg-listen": "^1.7.0", - "pino": "^9.7.0", + "pino": "^10.3.1", "pino-logflare": "^0.5.2", + "platformatic": "^3.52.0", "postgres-migrations": "^5.3.0", - "prom-client": "^15.1.3", + "pprof-format": "^2.2.1", + "wattpm": "^3.52.0", "xml2js": "^0.6.2" }, "devDependencies": { - "@aws-sdk/s3-presigned-post": "3.654.0", - "@babel/preset-env": "^7.26.9", - "@babel/preset-typescript": "^7.27.0", + "@aws-sdk/s3-presigned-post": "^3.1023.0", + "@biomejs/biome": "^2.4.4", "@types/async-retry": "^1.4.5", "@types/busboy": "^1.3.0", - "@types/crypto-js": "^4.1.1", - "@types/fs-extra": "^9.0.13", - "@types/glob": "^8.1.0", - "@types/jest": "^29.2.1", + "@types/cloneable-readable": "^2.0.3", "@types/js-yaml": "^4.0.5", - "@types/multistream": "^4.1.3", - "@types/mustache": "^4.2.2", - "@types/node": "^20.11.5", + "@types/json-bigint": "^1.0.4", + "@types/node": "^24.12.0", "@types/pg": "^8.6.4", "@types/stream-buffers": "^3.0.7", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.7.0", - "@typescript-eslint/parser": "^8.7.0", - "babel-jest": "^29.7.0", + "@vitest/coverage-v8": "^4.1.4", "esbuild": "^0.25.8", - "eslint": "^8.9.0", - "eslint-config-prettier": "^8.10.0", - "eslint-plugin-prettier": "^4.2.1", "form-data": "^4.0.0", - "jest": "^29.7.0", "js-yaml": "^4.1.0", "json-schema-to-ts": "^3.0.0", - "mustache": "^4.2.0", - "pino-pretty": "^8.1.0", + "patch-package": "^8.0.0", + "pino-pretty": "^13.1.3", "prettier": "^2.8.8", "resolve-tspaths": "^0.8.19", "stream-buffers": "^3.0.2", - "ts-jest": "^29.3.2", - "ts-node-dev": "^1.1.8", "tsx": "^4.16.0", "tus-js-client": "^3.1.0", - "typescript": "5.2.2" + "typescript": "5.9.3", + "vitest": "^4.1.4" }, + "version": "1.11.2", + "description": "Supabase Storage Service", + "main": "index.js", + "author": "Supabase", + "license": "ISC", "bin": "./dist/server.js", - "overrides": { - "@aws-sdk/client-s3": "3.654.0" + "engines": { + "node": ">=24.0.0", + "npm": "11.12.1" } } diff --git a/patches/knex+3.1.0.patch b/patches/knex+3.1.0.patch new file mode 100644 index 000000000..4f3988cfd --- /dev/null +++ b/patches/knex+3.1.0.patch @@ -0,0 +1,287 @@ +diff --git a/node_modules/knex/lib/dialects/postgres/index.js b/node_modules/knex/lib/dialects/postgres/index.js +index 8b73cbb..7433a62 100644 +--- a/node_modules/knex/lib/dialects/postgres/index.js ++++ b/node_modules/knex/lib/dialects/postgres/index.js +@@ -260,21 +260,57 @@ class Client_PG extends Client { + } + + async cancelQuery(connectionToKill) { +- const conn = await this.acquireRawConnection(); ++ const processID = connectionToKill?.processID; ++ const secretKey = connectionToKill?.secretKey; + +- try { +- return await this._wrappedCancelQueryCall(conn, connectionToKill); +- } finally { +- await this.destroyRawConnection(conn).catch((err) => { +- this.logger.warn(`Connection Error: ${err}`); +- }); ++ if (!processID || !secretKey) { ++ this.logger.warn('[PG cancelQuery] Missing processID or secretKey'); ++ return; + } +- } +- _wrappedCancelQueryCall(conn, connectionToKill) { +- return this._query(conn, { +- sql: 'SELECT pg_cancel_backend($1);', +- bindings: [connectionToKill.processID], +- options: {}, ++ ++ // Use native PostgreSQL cancel protocol (works with PgBouncer) ++ const Connection = require('pg/lib/connection'); ++ const cancelConnection = new Connection(); ++ ++ const host = connectionToKill.host || this.config.connection?.host || 'localhost'; ++ const port = connectionToKill.port || this.config.connection?.port || 5432; ++ ++ return new Promise((resolve) => { ++ let resolved = false; ++ const done = () => { ++ if (resolved) return; ++ resolved = true; ++ cancelConnection.end(); ++ resolve(); ++ }; ++ ++ // Timeout in case something goes wrong ++ const timeout = setTimeout(() => { ++ this.logger.warn('[PG cancelQuery] Cancel request timed out'); ++ done(); ++ }, 5000); ++ ++ cancelConnection.on('error', (err) => { ++ clearTimeout(timeout); ++ this.logger.warn(`[PG cancelQuery] Cancel error: ${err.message}`); ++ done(); ++ }); ++ ++ cancelConnection.on('connect', () => { ++ cancelConnection.cancel(processID, secretKey); ++ }); ++ ++ cancelConnection.on('end', () => { ++ clearTimeout(timeout); ++ done(); ++ }); ++ ++ // Connect and send cancel request ++ if (host.indexOf('/') === 0) { ++ cancelConnection.connect(host + '/.s.PGSQL.' + port); ++ } else { ++ cancelConnection.connect(port, host); ++ } + }); + } + +diff --git a/node_modules/knex/lib/execution/runner.js b/node_modules/knex/lib/execution/runner.js +index ed01652..ea5e589 100644 +--- a/node_modules/knex/lib/execution/runner.js ++++ b/node_modules/knex/lib/execution/runner.js +@@ -144,6 +144,11 @@ class Runner { + queryPromise = timeout(queryPromise, obj.timeout); + } + ++ // Handle abort signal if provided ++ if (obj.abortSignal) { ++ queryPromise = this._wrapWithAbortSignal(queryPromise, obj.abortSignal, obj); ++ } ++ + // Await the return value of client.processResponse; in the case of sqlite3's + // dropColumn()/renameColumn(), it will be a Promise for the transaction + // containing the complete rename procedure. +@@ -225,6 +230,66 @@ class Runner { + }); + } + ++ // Wraps a query promise with abort signal handling ++ _wrapWithAbortSignal(queryPromise, abortSignal, obj) { ++ // If signal is already aborted, reject immediately ++ if (abortSignal.aborted) { ++ return Promise.reject(this._createAbortError()); ++ } ++ ++ return new Promise((resolve, reject) => { ++ let isResolved = false; ++ ++ // Create abort listener ++ const onAbort = async () => { ++ if (isResolved) return; ++ isResolved = true; ++ ++ // Clean up the listener ++ abortSignal.removeEventListener('abort', onAbort); ++ ++ try { ++ // Cancel the query using the existing infrastructure ++ await this.client.cancelQuery(this.connection); ++ ++ // Mark connection as disposed to prevent reuse ++ this.connection.__knex__disposed = true; ++ } catch (cancelError) { ++ // Mark connection as disposed if cancellation fails ++ this.connection.__knex__disposed = cancelError; ++ } ++ ++ // Reject with AbortError ++ reject(this._createAbortError()); ++ }; ++ ++ abortSignal.addEventListener('abort', onAbort); ++ ++ // Handle the original promise ++ queryPromise ++ .then((result) => { ++ if (isResolved) return; ++ isResolved = true; ++ abortSignal.removeEventListener('abort', onAbort); ++ resolve(result); ++ }) ++ .catch((error) => { ++ if (isResolved) return; ++ isResolved = true; ++ abortSignal.removeEventListener('abort', onAbort); ++ reject(error); ++ }); ++ }); ++ } ++ ++ // Creates a standard AbortError ++ _createAbortError() { ++ const error = new Error('Query was aborted'); ++ error.name = 'AbortError'; ++ error.code = 'ABORT_ERR'; ++ return error; ++ } ++ + // In the case of the "schema builder" we call `queryArray`, which runs each + // of the queries in sequence. + async queryArray(queries) { +diff --git a/node_modules/knex/lib/query/querybuilder.js b/node_modules/knex/lib/query/querybuilder.js +index 0d3cff5..af25082 100644 +--- a/node_modules/knex/lib/query/querybuilder.js ++++ b/node_modules/knex/lib/query/querybuilder.js +@@ -118,6 +118,34 @@ class Builder extends EventEmitter { + return this; + } + ++ abortOnSignal(signal) { ++ // If no signal provided, just return this (no-op) ++ if (!signal) { ++ return this; ++ } ++ ++ // Check if AbortSignal is available (Node 15+) ++ if (typeof AbortSignal === 'undefined') { ++ throw new Error( ++ 'AbortSignal is not available. Node.js 15 or higher is required.' ++ ); ++ } ++ ++ if (!(signal instanceof AbortSignal)) { ++ throw new Error('Expected signal to be an instance of AbortSignal'); ++ } ++ ++ if (signal.aborted) { ++ throw new Error('Signal is already aborted'); ++ } ++ ++ // Assert that the client can cancel queries ++ this.client.assertCanCancelQuery(); ++ ++ this._abortSignal = signal; ++ return this; ++ } ++ + // With + // ------ + isValidStatementArg(statement) { +diff --git a/node_modules/knex/lib/query/querycompiler.js b/node_modules/knex/lib/query/querycompiler.js +index ca79dac..f4fd327 100644 +--- a/node_modules/knex/lib/query/querycompiler.js ++++ b/node_modules/knex/lib/query/querycompiler.js +@@ -54,6 +54,7 @@ class QueryCompiler { + this.queryComments = builder._comments; + this.timeout = builder._timeout || false; + this.cancelOnTimeout = builder._cancelOnTimeout || false; ++ this.abortSignal = builder._abortSignal || null; + this.grouped = groupBy(builder._statements, 'grouping'); + this.formatter = client.formatter(builder); + // Used when the insert call is empty. +@@ -79,6 +80,7 @@ class QueryCompiler { + options: reduce(this.options, assign, {}), + timeout: this.timeout, + cancelOnTimeout: this.cancelOnTimeout, ++ abortSignal: this.abortSignal, + bindings: this.bindingsHolder.bindings || [], + __knexQueryUid: nanoid(), + }; +diff --git a/node_modules/knex/lib/raw.js b/node_modules/knex/lib/raw.js +index e4c2feb..edd2f3d 100644 +--- a/node_modules/knex/lib/raw.js ++++ b/node_modules/knex/lib/raw.js +@@ -58,6 +58,34 @@ class Raw extends EventEmitter { + return this; + } + ++ abortOnSignal(signal) { ++ // If no signal provided, just return this (no-op) ++ if (!signal) { ++ return this; ++ } ++ ++ // Check if AbortSignal is available (Node 15+) ++ if (typeof AbortSignal === 'undefined') { ++ throw new Error( ++ 'AbortSignal is not available. Node.js 15 or higher is required.' ++ ); ++ } ++ ++ if (!(signal instanceof AbortSignal)) { ++ throw new Error('Expected signal to be an instance of AbortSignal'); ++ } ++ ++ if (signal.aborted) { ++ throw new Error('Signal is already aborted'); ++ } ++ ++ // Assert that the client can cancel queries ++ this.client.assertCanCancelQuery(); ++ ++ this._abortSignal = signal; ++ return this; ++ } ++ + // Wraps the current sql with `before` and `after`. + wrap(before, after) { + this._wrappedBefore = before; +@@ -101,6 +129,10 @@ class Raw extends EventEmitter { + } + } + ++ if (this._abortSignal) { ++ obj.abortSignal = this._abortSignal; ++ } ++ + obj.bindings = obj.bindings || []; + if (helpers.containsUndefined(obj.bindings)) { + const undefinedBindingIndices = helpers.getUndefinedIndices( +diff --git a/node_modules/knex/types/index.d.ts b/node_modules/knex/types/index.d.ts +index d191bd4..2c0acc1 100644 +--- a/node_modules/knex/types/index.d.ts ++++ b/node_modules/knex/types/index.d.ts +@@ -2111,6 +2111,7 @@ declare namespace Knex { + extends events.EventEmitter, + ChainableInterface> { + timeout(ms: number, options?: { cancel?: boolean }): Raw; ++ abortOnSignal(signal?: AbortSignal): Raw; + wrap(before: string, after: string): Raw; + toSQL(): Sql; + queryContext(context: any): Raw; +@@ -2252,6 +2253,7 @@ declare namespace Knex { + ms: number, + options?: { cancel?: boolean } + ): QueryBuilder; ++ abortOnSignal(signal?: AbortSignal): QueryBuilder; + } + + interface Sql { diff --git a/scripts/ensure-npm-version.cjs b/scripts/ensure-npm-version.cjs new file mode 100644 index 000000000..2e67ce0d6 --- /dev/null +++ b/scripts/ensure-npm-version.cjs @@ -0,0 +1,37 @@ +#!/usr/bin/env node + +const { execFileSync } = require('node:child_process') +const { readFileSync } = require('node:fs') +const { resolve } = require('node:path') + +const packageJsonPath = resolve(process.cwd(), 'package.json') +const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) +const packageManager = packageJson.packageManager +const match = /^npm@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.exec(packageManager || '') + +if (!match) { + console.error('package.json packageManager must be npm@') + process.exit(1) +} + +const expectedVersion = match[1] + +const currentVersion = () => execFileSync('npm', ['--version'], { encoding: 'utf8' }).trim() + +const beforeVersion = currentVersion() + +if (beforeVersion !== expectedVersion) { + console.log(`Installing npm@${expectedVersion} over npm@${beforeVersion}`) + execFileSync('npm', ['install', '--global', '--engine-strict=false', `npm@${expectedVersion}`], { + stdio: 'inherit', + }) +} + +const afterVersion = currentVersion() + +if (afterVersion !== expectedVersion) { + console.error(`Expected npm@${expectedVersion}, found npm@${afterVersion}`) + process.exit(1) +} + +console.log(`Using npm@${afterVersion}`) diff --git a/src/admin-app.test.ts b/src/admin-app.test.ts new file mode 100644 index 000000000..94881bbb1 --- /dev/null +++ b/src/admin-app.test.ts @@ -0,0 +1,54 @@ +import { vi } from 'vitest' + +const getGlobal = vi.hoisted(() => vi.fn()) + +vi.mock('@platformatic/globals', () => ({ + getGlobal, +})) + +async function buildAdminApp() { + vi.resetModules() + const { default: buildAdmin } = await import('./admin-app') + const app = buildAdmin({}) + await app.ready() + return app +} + +describe('admin app pprof registration', () => { + it('does not register pprof endpoints outside Watt', async () => { + getGlobal.mockReturnValue(undefined) + + const app = await buildAdminApp() + + try { + const response = await app.inject({ + method: 'GET', + url: '/debug/pprof/profile', + }) + + expect(response.statusCode).toBe(404) + } finally { + await app.close() + } + }) + + it('registers pprof endpoints under Watt', async () => { + getGlobal.mockReturnValue({ + applicationId: 'storage', + workerId: 0, + }) + + const app = await buildAdminApp() + + try { + const response = await app.inject({ + method: 'GET', + url: '/debug/pprof/profile', + }) + + expect(response.statusCode).toBe(401) + } finally { + await app.close() + } + }) +}) diff --git a/src/admin-app.ts b/src/admin-app.ts index 7332b122d..ebc1c3b77 100644 --- a/src/admin-app.ts +++ b/src/admin-app.ts @@ -1,54 +1,72 @@ +import fastifySwagger from '@fastify/swagger' +import fastifySwaggerUi from '@fastify/swagger-ui' +import { handleMetricsRequest } from '@internal/monitoring/otel-metrics' +import { getGlobal } from '@platformatic/globals' import fastify, { FastifyInstance, FastifyServerOptions } from 'fastify' -import { routes, plugins, setErrorHandler } from './http' -import { Registry } from 'prom-client' +import { getConfig } from './config' +import { plugins, routes, setErrorHandler } from './http' -declare module 'fastify-metrics' { - interface IFastifyMetrics { - getCustomDefaultMetricsRegistries(): Registry[] - getCustomRouteMetricsRegistries(): Registry[] - } +interface buildOpts extends FastifyServerOptions { + exposeDocs?: boolean } -const build = (opts: FastifyServerOptions = {}, appInstance?: FastifyInstance): FastifyInstance => { +const { version, prometheusMetricsEnabled } = getConfig() + +const build = (opts: buildOpts = {}): FastifyInstance => { const app = fastify(opts) + const isRunningUnderWatt = typeof getGlobal()?.applicationId === 'string' + + if (opts.exposeDocs) { + app.register(fastifySwagger, { + exposeHeadRoutes: true, + openapi: { + info: { + title: 'Supabase Storage Admin API', + description: 'Admin API documentation for Supabase Storage', + version, + }, + tags: [ + { name: 'tenant', description: 'Tenant management' }, + { name: 'object', description: 'Object management' }, + { name: 'jwks', description: 'JWKS configuration' }, + { name: 'migration', description: 'Database migrations' }, + { name: 's3-credentials', description: 'S3 credentials management' }, + { name: 'queue', description: 'Queue management' }, + { name: 'metrics', description: 'Metrics configuration' }, + ...(isRunningUnderWatt + ? [{ name: 'pprof', description: 'Runtime profiling via Watt control APIs' }] + : []), + ], + }, + }) + + app.register(fastifySwaggerUi, { + routePrefix: '/documentation', + }) + } + app.register(plugins.signals) app.register(plugins.adminTenantId) - app.register(plugins.logRequest({ excludeUrls: ['/status', '/metrics', '/health'] })) + app.register(plugins.logRequest({ excludeUrls: ['/status', '/metrics', '/health', '/version'] })) app.register(routes.tenants, { prefix: 'tenants' }) app.register(routes.objects, { prefix: 'tenants' }) app.register(routes.jwks, { prefix: 'tenants' }) app.register(routes.migrations, { prefix: 'migrations' }) + if (isRunningUnderWatt) { + app.register(routes.pprof, { prefix: 'debug/pprof' }) + } app.register(routes.s3Credentials, { prefix: 's3' }) app.register(routes.queue, { prefix: 'queue' }) + app.register(routes.metricsConfig, { prefix: 'metrics' }) - let registriesToMerge: Registry[] = [] - - if (appInstance) { - app.get('/metrics', async (req, reply) => { - if (registriesToMerge.length === 0) { - const globalRegistry = appInstance.metrics.client.register - const defaultRegistries = appInstance.metrics.getCustomDefaultMetricsRegistries() - const routeRegistries = appInstance.metrics.getCustomRouteMetricsRegistries() - - registriesToMerge = Array.from( - new Set([globalRegistry, ...defaultRegistries, ...routeRegistries]) - ) - } - - if (registriesToMerge.length === 1) { - const data = await registriesToMerge[0].metrics() - return reply.type(registriesToMerge[0].contentType).send(data) - } - const merged = appInstance.metrics.client.Registry.merge(registriesToMerge) - - const data = await merged.metrics() - - return reply.type(merged.contentType).send(data) - }) - } else { - app.register(plugins.metrics({ enabledEndpoint: true })) + // Register /metrics endpoint - uses OTel Prometheus exporter + if (prometheusMetricsEnabled) { + app.get('/metrics', handleMetricsRequest) } + app.get('/version', (_, reply) => { + reply.send(version) + }) app.get('/status', async (_, response) => response.status(200).send()) setErrorHandler(app) diff --git a/src/app.ts b/src/app.ts index dc99fe7eb..b2275f0d2 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,8 +1,8 @@ -import fastify, { FastifyInstance, FastifyServerOptions } from 'fastify' import fastifySwagger from '@fastify/swagger' import fastifySwaggerUi from '@fastify/swagger-ui' -import { routes, schemas, plugins, setErrorHandler } from './http' +import fastify, { FastifyInstance, FastifyServerOptions } from 'fastify' import { getConfig } from './config' +import { plugins, routes, schemas, setErrorHandler } from './http' interface buildOpts extends FastifyServerOptions { exposeDocs?: boolean @@ -25,11 +25,12 @@ const build = (opts: buildOpts = {}): FastifyInstance => { if (opts.exposeDocs) { app.register(fastifySwagger, { + exposeHeadRoutes: true, openapi: { info: { title: 'Supabase Storage API', description: 'API documentation for Supabase Storage', - version: version, + version, }, tags: [ { name: 'object', description: 'Object end-points' }, @@ -37,6 +38,10 @@ const build = (opts: buildOpts = {}): FastifyInstance => { { name: 's3', description: 'S3 end-points' }, { name: 'transformation', description: 'Image transformation' }, { name: 'resumable', description: 'Resumable Upload end-points' }, + { name: 'cdn', description: 'CDN cache management' }, + { name: 'health', description: 'Health check end-points' }, + { name: 'iceberg', description: 'Apache Iceberg REST catalog' }, + { name: 'vector', description: 'Vector storage and search' }, ], }, }) @@ -46,15 +51,30 @@ const build = (opts: buildOpts = {}): FastifyInstance => { }) } + const excludedRoutesFromMonitoring = [ + '/status', + '/metrics', + '/health', + '/healthcheck', + '/version', + '/documentation', + ] + // add in common schemas app.addSchema(schemas.authSchema) app.addSchema(schemas.errorSchema) app.register(plugins.signals) app.register(plugins.tenantId) - app.register(plugins.metrics({ enabledEndpoint: !isMultitenant })) + app.register( + plugins.metrics({ + enabledEndpoint: !isMultitenant, + excludeRoutes: excludedRoutesFromMonitoring, + }) + ) app.register(plugins.tracing) - app.register(plugins.logRequest({ excludeUrls: ['/status', '/metrics', '/health'] })) + app.register(plugins.logRequest({ excludeUrls: excludedRoutesFromMonitoring })) + app.register(plugins.headerValidator({ excludeUrls: excludedRoutesFromMonitoring })) app.register(routes.tus, { prefix: 'upload/resumable' }) app.register(routes.bucket, { prefix: 'bucket' }) app.register(routes.object, { prefix: 'object' }) @@ -62,7 +82,8 @@ const build = (opts: buildOpts = {}): FastifyInstance => { app.register(routes.s3, { prefix: 's3' }) app.register(routes.cdn, { prefix: 'cdn' }) app.register(routes.healthcheck, { prefix: 'health' }) - app.register(routes.iceberg, { prefix: 'iceberg/v1' }) + app.register(routes.iceberg, { prefix: 'iceberg' }) + app.register(routes.vector, { prefix: 'vector' }) setErrorHandler(app) diff --git a/src/config.test.ts b/src/config.test.ts new file mode 100644 index 000000000..c74400557 --- /dev/null +++ b/src/config.test.ts @@ -0,0 +1,95 @@ +import { vi } from 'vitest' + +const SAMPLE_RATE_ENV_KEYS = [ + 'MULTI_TENANT', + 'IS_MULTITENANT', + 'TENANT_POOL_CACHE_HIT_LOG_SAMPLE_RATE', + 'TENANT_POOL_CACHE_MISS_LOG_SAMPLE_RATE', +] as const + +type SampleRateEnvKey = (typeof SAMPLE_RATE_ENV_KEYS)[number] + +const originalEnv = new Map() + +function setSampleRateEnv(env: Partial>) { + for (const key of SAMPLE_RATE_ENV_KEYS) { + delete process.env[key] + } + + process.env.MULTI_TENANT = 'true' + + for (const [key, value] of Object.entries(env)) { + process.env[key] = value + } +} + +describe('config sample rate parsing', () => { + beforeAll(() => { + for (const key of SAMPLE_RATE_ENV_KEYS) { + originalEnv.set(key, process.env[key]) + } + }) + + afterEach(() => { + for (const key of SAMPLE_RATE_ENV_KEYS) { + const value = originalEnv.get(key) + + if (value === undefined) { + delete process.env[key] + } else { + process.env[key] = value + } + } + + vi.resetModules() + }) + + test('defaults tenant pool cache log sample rates to zero', async () => { + setSampleRateEnv({}) + + const { getConfig } = await import('./config') + const config = getConfig({ reload: true }) + + expect(config.tenantPoolCacheHitLogSampleRate).toBe(0) + expect(config.tenantPoolCacheMissLogSampleRate).toBe(0) + }) + + test('parses fractional tenant pool cache log sample rates', async () => { + setSampleRateEnv({ + TENANT_POOL_CACHE_HIT_LOG_SAMPLE_RATE: '0.25', + TENANT_POOL_CACHE_MISS_LOG_SAMPLE_RATE: '0.75', + }) + + const { getConfig } = await import('./config') + const config = getConfig({ reload: true }) + + expect(config.tenantPoolCacheHitLogSampleRate).toBe(0.25) + expect(config.tenantPoolCacheMissLogSampleRate).toBe(0.75) + }) + + test('clamps tenant pool cache log sample rates to zero and one', async () => { + setSampleRateEnv({ + TENANT_POOL_CACHE_HIT_LOG_SAMPLE_RATE: '-0.5', + TENANT_POOL_CACHE_MISS_LOG_SAMPLE_RATE: '1.5', + }) + + const { getConfig } = await import('./config') + const config = getConfig({ reload: true }) + + expect(config.tenantPoolCacheHitLogSampleRate).toBe(0) + expect(config.tenantPoolCacheMissLogSampleRate).toBe(1) + }) + + test('falls back to default tenant pool cache log sample rates for invalid values', async () => { + setSampleRateEnv({ + TENANT_POOL_CACHE_HIT_LOG_SAMPLE_RATE: 'nope', + TENANT_POOL_CACHE_MISS_LOG_SAMPLE_RATE: 'Infinity', + }) + + const { getConfig } = await import('./config') + const config = getConfig({ reload: true }) + + expect(config.tenantPoolCacheHitLogSampleRate).toBe(0) + expect(config.tenantPoolCacheMissLogSampleRate).toBe(0) + }) +}) diff --git a/src/config.ts b/src/config.ts index accb222d7..c560f5441 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,5 @@ -import dotenv from 'dotenv' import type { DBMigration } from '@internal/database/migrations' +import dotenv from 'dotenv' import { SignJWT } from 'jose' export type StorageBackendType = 'file' | 's3' @@ -53,6 +53,7 @@ export interface JwksConfig { type StorageConfigType = { isProduction: boolean version: string + numWorkers: number exposeDocs: boolean keepAliveTimeout: number headersTimeout: number @@ -63,19 +64,24 @@ type StorageConfigType = { uploadFileSizeLimitStandard?: number storageFilePath?: string storageFileEtagAlgorithm: 'mtime' | 'md5' + storageS3InternalTracesEnabled?: boolean storageS3MaxSockets: number + storageS3DisableChecksum: boolean + storageS3UploadQueueSize: number storageS3Bucket: string storageS3Endpoint?: string storageS3ForcePathStyle?: boolean storageS3Region: string storageS3ClientTimeout: number - storageS3DeleteConcurrency: number isMultitenant: boolean jwtSecret: string jwtAlgorithm: string jwtCachingEnabled: boolean jwtJWKS?: JwksConfig multitenantDatabaseUrl?: string + multitenantDatabasePoolUrl?: string + multitenantMaxConnections: number + multitenantDatabaseQueryTimeout: number dbAnonRole: string dbAuthenticatedRole: string dbServiceRole: string @@ -93,6 +99,10 @@ type StorageConfigType = { databaseMaxConnections: number databaseFreePoolAfterInactivity: number databaseConnectionTimeout: number + databaseEnableQueryCancellation: boolean + databaseStatementTimeout: number + databaseApplicationName: string + pgQueueApplicationName: string region: string requestTraceHeader?: string requestEtagHeaders: string[] @@ -105,6 +115,7 @@ type StorageConfigType = { requestUrlLengthLimit: number requestXForwardedHostRegExp?: string requestAllowXForwardedPrefix?: boolean + storagePublicUrl?: string logLevel?: string logflareEnabled?: boolean logflareApiKey?: string @@ -159,7 +170,6 @@ type StorageConfigType = { tusUseFileVersionSeparator: boolean tusAllowS3Tags: boolean tusLockType: 'postgres' | 's3' - defaultMetricsEnabled: boolean s3ProtocolEnabled: boolean s3ProtocolPrefix: string s3ProtocolAllowForwardedHeader: boolean @@ -167,7 +177,7 @@ type StorageConfigType = { s3ProtocolAccessKeyId?: string s3ProtocolAccessKeySecret?: string s3ProtocolNonCanonicalHostHeader?: string - s3Port?: number + s3OmitPrefixFromCanonicalUri?: string tracingEnabled?: boolean tracingMode?: string tracingTimeMinDuration: number @@ -175,10 +185,19 @@ type StorageConfigType = { tracingFeatures?: { upload: boolean } + prometheusMetricsEnabled: boolean + prometheusMetricsIncludeTenantId: boolean + tenantPoolCacheHitLogSampleRate: number + tenantPoolCacheMissLogSampleRate: number + otelMetricsEnabled: boolean + otelMetricsTemporality: 'DELTA' | 'CUMULATIVE' + otelMetricsExportIntervalMs: number cdnPurgeEndpointURL?: string cdnPurgeEndpointKey?: string + icebergEnabled: boolean icebergWarehouse: string + icebergShards: string[] icebergCatalogUrl: string icebergCatalogAuthType: IcebergCatalogAuthType icebergCatalogToken?: string @@ -188,6 +207,12 @@ type StorageConfigType = { icebergBucketDetectionSuffix: string icebergBucketDetectionMode: 'BUCKET' | 'FULL_PATH' icebergS3DeleteEnabled: boolean + + vectorEnabled: boolean + vectorS3Buckets: string[] + vectorBucketRegion?: string + vectorMaxBucketsCount: number + vectorMaxIndexesCount: number } function getOptionalConfigFromEnv(key: string, fallback?: string): string | undefined { @@ -238,6 +263,7 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType { const isMultitenant = getOptionalConfigFromEnv('MULTI_TENANT', 'IS_MULTITENANT') === 'true' config = { + numWorkers: envNumber(getOptionalConfigFromEnv('WORKERS_NUM'), 1), isProduction: process.env.NODE_ENV === 'production', exposeDocs: getOptionalConfigFromEnv('EXPOSE_DOCS') !== 'false', isMultitenant, @@ -264,6 +290,7 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType { ), requestAllowXForwardedPrefix: getOptionalConfigFromEnv('REQUEST_ALLOW_X_FORWARDED_PATH') === 'true', + storagePublicUrl: getOptionalConfigFromEnv('STORAGE_PUBLIC_URL'), requestUrlLengthLimit: Number(getOptionalConfigFromEnv('REQUEST_URL_LENGTH_LIMIT', 'URL_LENGTH_LIMIT')) || 7_500, requestTraceHeader: getOptionalConfigFromEnv('REQUEST_TRACE_HEADER', 'REQUEST_ID_HEADER'), @@ -328,7 +355,8 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType { s3ProtocolNonCanonicalHostHeader: getOptionalConfigFromEnv( 'S3_PROTOCOL_NON_CANONICAL_HOST_HEADER' ), - s3Port: Number(getOptionalConfigFromEnv('S3_PORT')), + s3OmitPrefixFromCanonicalUri: + getOptionalConfigFromEnv('S3_OMIT_PREFIX_FROM_CANONICAL_URI') === 'true' ? "/s3" : undefined, // Storage storageBackendType: getOptionalConfigFromEnv('STORAGE_BACKEND') as StorageBackendType, emptyBucketMax: parseInt(getOptionalConfigFromEnv('STORAGE_EMPTY_BUCKET_MAX') || '200000', 10), @@ -345,6 +373,11 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType { getOptionalConfigFromEnv('STORAGE_S3_MAX_SOCKETS', 'GLOBAL_S3_MAX_SOCKETS') || '200', 10 ), + storageS3DisableChecksum: getOptionalConfigFromEnv('STORAGE_S3_DISABLE_CHECKSUM') === 'true', + storageS3UploadQueueSize: + envNumber(getOptionalConfigFromEnv('STORAGE_S3_UPLOAD_QUEUE_SIZE')) ?? 2, + storageS3InternalTracesEnabled: + getOptionalConfigFromEnv('STORAGE_S3_ENABLED_METRICS') === 'true', storageS3Bucket: getOptionalConfigFromEnv('STORAGE_S3_BUCKET', 'GLOBAL_S3_BUCKET'), storageS3Endpoint: getOptionalConfigFromEnv('STORAGE_S3_ENDPOINT', 'GLOBAL_S3_ENDPOINT'), storageS3ForcePathStyle: @@ -352,10 +385,6 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType { 'true', storageS3Region: getOptionalConfigFromEnv('STORAGE_S3_REGION', 'REGION') as string, storageS3ClientTimeout: Number(getOptionalConfigFromEnv('STORAGE_S3_CLIENT_TIMEOUT') || `0`), - storageS3DeleteConcurrency: parseInt( - getOptionalConfigFromEnv('STORAGE_S3_DELETE_CONCURRENCY') || '8', - 10 - ), // DB - Migrations dbAnonRole: getOptionalConfigFromEnv('DB_ANON_ROLE') || 'anon', @@ -378,6 +407,21 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType { 'DATABASE_MULTITENANT_URL', 'MULTITENANT_DATABASE_URL' ), + multitenantDatabasePoolUrl: getOptionalConfigFromEnv( + 'DATABASE_MULTITENANT_POOL_URL', + 'MULTITENANT_DATABASE_POOL_URL' + ), + multitenantMaxConnections: envNumber( + getOptionalConfigFromEnv( + 'DATABASE_MULTITENANT_MAX_CONNECTIONS', + 'MULTITENANT_DATABASE_MAX_CONNECTIONS' + ), + 10 + ), + multitenantDatabaseQueryTimeout: envNumber( + getOptionalConfigFromEnv('DATABASE_MULTITENANT_QUERY_TIMEOUT'), + 10_000 + ), databaseSSLRootCert: getOptionalConfigFromEnv('DATABASE_SSL_ROOT_CERT'), databaseURL: getOptionalIfMultitenantConfigFromEnv('DATABASE_URL') || '', databasePoolURL: getOptionalConfigFromEnv('DATABASE_POOL_URL') || '', @@ -394,6 +438,18 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType { getOptionalConfigFromEnv('DATABASE_CONNECTION_TIMEOUT') || '3000', 10 ), + databaseEnableQueryCancellation: + getOptionalConfigFromEnv('DATABASE_ENABLE_QUERY_CANCELLATION') === 'true', + databaseStatementTimeout: parseInt( + getOptionalConfigFromEnv('DATABASE_STATEMENT_TIMEOUT') || '30000', + 10 + ), + databaseApplicationName: + getOptionalConfigFromEnv('DATABASE_APPLICATION_NAME') || + `Supabase Storage API ${getOptionalConfigFromEnv('VERSION') || '0.0.0'}`, + pgQueueApplicationName: + getOptionalConfigFromEnv('PG_QUEUE_APPLICATION_NAME') || + `Supabase Storage PgBoss ${getOptionalConfigFromEnv('VERSION') || '0.0.0'}`, // CDN cdnPurgeEndpointURL: getOptionalConfigFromEnv('CDN_PURGE_ENDPOINT_URL'), @@ -405,8 +461,13 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType { logflareApiKey: getOptionalConfigFromEnv('LOGFLARE_API_KEY'), logflareSourceToken: getOptionalConfigFromEnv('LOGFLARE_SOURCE_TOKEN'), logflareBatchSize: parseInt(getOptionalConfigFromEnv('LOGFLARE_BATCH_SIZE') || '200', 10), - defaultMetricsEnabled: !( - getOptionalConfigFromEnv('DEFAULT_METRICS_ENABLED', 'ENABLE_DEFAULT_METRICS') === 'false' + tenantPoolCacheHitLogSampleRate: envSampleRate( + getOptionalConfigFromEnv('TENANT_POOL_CACHE_HIT_LOG_SAMPLE_RATE'), + 0 + ), + tenantPoolCacheMissLogSampleRate: envSampleRate( + getOptionalConfigFromEnv('TENANT_POOL_CACHE_MISS_LOG_SAMPLE_RATE'), + 0 ), tracingEnabled: getOptionalConfigFromEnv('TRACING_ENABLED') === 'true', tracingMode: getOptionalConfigFromEnv('TRACING_MODE') ?? 'basic', @@ -419,10 +480,22 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType { upload: getOptionalConfigFromEnv('TRACING_FEATURE_UPLOAD') === 'true', }, + // OpenTelemetry Metrics + prometheusMetricsEnabled: getOptionalConfigFromEnv('PROMETHEUS_METRICS_ENABLED') === 'true', + prometheusMetricsIncludeTenantId: + getOptionalConfigFromEnv('PROMETHEUS_METRICS_INCLUDE_TENANT') === 'true', + otelMetricsEnabled: getOptionalConfigFromEnv('OTEL_METRICS_ENABLED') === 'true', + otelMetricsTemporality: getOptionalConfigFromEnv('OTEL_METRICS_TEMPORALITY') || 'CUMULATIVE', + otelMetricsExportIntervalMs: parseInt( + getOptionalConfigFromEnv('OTEL_METRICS_EXPORT_INTERVAL_MS') || '60000', + 10 + ), + // Queue pgQueueEnable: getOptionalConfigFromEnv('PG_QUEUE_ENABLE', 'ENABLE_QUEUE_EVENTS') === 'true', pgQueueEnableWorkers: getOptionalConfigFromEnv('PG_QUEUE_WORKERS_ENABLE') !== 'false', - pgQueueReadWriteTimeout: Number(getOptionalConfigFromEnv('PG_QUEUE_READ_WRITE_TIMEOUT')) || 0, + pgQueueReadWriteTimeout: + envNumber(getOptionalConfigFromEnv('PG_QUEUE_READ_WRITE_TIMEOUT')) ?? 5000, pgQueueMaxConnections: Number(getOptionalConfigFromEnv('PG_QUEUE_MAX_CONNECTIONS')) || 4, pgQueueConnectionURL: getOptionalConfigFromEnv('PG_QUEUE_CONNECTION_URL'), pgQueueDeleteAfterDays: parseInt( @@ -430,7 +503,7 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType { 10 ), pgQueueDeleteAfterHours: - Number(getOptionalConfigFromEnv('PG_QUEUE_DELETE_AFTER_HOURS')) || undefined, + envNumber(getOptionalConfigFromEnv('PG_QUEUE_DELETE_AFTER_HOURS')) || undefined, pgQueueArchiveCompletedAfterSeconds: parseInt( getOptionalConfigFromEnv('PG_QUEUE_ARCHIVE_COMPLETED_AFTER_SECONDS') || '7200', 10 @@ -513,7 +586,9 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType { 10 ), + icebergEnabled: getOptionalConfigFromEnv('ICEBERG_ENABLED') === 'true', icebergWarehouse: getOptionalConfigFromEnv('ICEBERG_WAREHOUSE') || '', + icebergShards: getOptionalConfigFromEnv('ICEBERG_SHARDS')?.trim().split(',') || [], icebergCatalogUrl: getOptionalConfigFromEnv('ICEBERG_CATALOG_URL') || `https://s3tables.ap-southeast-1.amazonaws.com/iceberg/v1`, @@ -531,6 +606,12 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType { ), icebergMaxTableCount: parseInt(getOptionalConfigFromEnv('ICEBERG_MAX_TABLES') || '10', 10), icebergS3DeleteEnabled: getOptionalConfigFromEnv('ICEBERG_S3_DELETE_ENABLED') === 'true', + + vectorEnabled: getOptionalConfigFromEnv('VECTOR_ENABLED') === 'true', + vectorS3Buckets: getOptionalConfigFromEnv('VECTOR_S3_BUCKETS')?.trim()?.split(',') || [], + vectorBucketRegion: getOptionalConfigFromEnv('VECTOR_BUCKET_REGION') || undefined, + vectorMaxBucketsCount: parseInt(getOptionalConfigFromEnv('VECTOR_MAX_BUCKETS') || '10', 10), + vectorMaxIndexesCount: parseInt(getOptionalConfigFromEnv('VECTOR_MAX_INDEXES') || '20', 10), } as StorageConfigType const serviceKey = getOptionalConfigFromEnv('SERVICE_KEY') || '' @@ -567,3 +648,27 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType { return config } + +function envNumber(value: string | undefined, defaultValue?: number): number | undefined { + if (!value) { + return defaultValue + } + const parsed = parseInt(value, 10) + if (isNaN(parsed)) { + return defaultValue + } + return parsed +} + +function envSampleRate(value: string | undefined, defaultValue: number): number { + if (!value) { + return defaultValue + } + + const parsed = Number(value) + if (!Number.isFinite(parsed)) { + return defaultValue + } + + return Math.min(Math.max(parsed, 0), 1) +} diff --git a/src/http/error-handler.ts b/src/http/error-handler.ts index aebc52d98..a99cd87cd 100644 --- a/src/http/error-handler.ts +++ b/src/http/error-handler.ts @@ -1,7 +1,7 @@ -import { FastifyInstance } from 'fastify' import { FastifyError } from '@fastify/error' +import { ErrorCode, isRenderableError, StorageBackendError, StorageError } from '@internal/errors' +import { FastifyInstance } from 'fastify' import { DatabaseError } from 'pg' -import { ErrorCode, isRenderableError, StorageError } from '@internal/errors' /** * The global error handler for all the uncaught exceptions within a request. @@ -50,12 +50,15 @@ export const setErrorHandler = ( const statusCode = options?.respectStatusCode ? parseInt(renderableError.statusCode, 10) : error.userStatusCode - ? error.userStatusCode - : renderableError.statusCode === '500' - ? 500 - : 400 + ? error.userStatusCode + : renderableError.statusCode === '500' + ? 500 + : 400 - if (renderableError.code === ErrorCode.AbortedTerminate) { + if ( + renderableError.code === ErrorCode.AbortedTerminate || + (error instanceof StorageBackendError && error.shouldCloseConnection()) + ) { reply.header('Connection', 'close') reply.raw.once('finish', () => { @@ -78,6 +81,18 @@ export const setErrorHandler = ( // Fastify errors if ('statusCode' in error) { const err = error as FastifyError + + if (err.code === 'FST_ERR_CTP_INVALID_MEDIA_TYPE') { + return reply.status(400).send( + formatter({ + statusCode: '415', + code: ErrorCode.InvalidMimeType, + error: 'invalid_mime_type', + message: 'Invalid Content-Type header', + }) + ) + } + return reply.status(err.statusCode || 500).send( formatter({ statusCode: `${err.statusCode}`, diff --git a/src/http/index.ts b/src/http/index.ts index bb9140e91..02a34d5d1 100644 --- a/src/http/index.ts +++ b/src/http/index.ts @@ -1,4 +1,4 @@ -export * as routes from './routes' +export * from './error-handler' export * as plugins from './plugins' +export * as routes from './routes' export * as schemas from './schemas' -export * from './error-handler' diff --git a/src/http/plugins/apikey.ts b/src/http/plugins/apikey.ts index 007be213e..d3a363064 100644 --- a/src/http/plugins/apikey.ts +++ b/src/http/plugins/apikey.ts @@ -7,7 +7,7 @@ export default fastifyPlugin( const apiKeys = new Set(adminApiKeys.split(',')) fastify.addHook('onRequest', async (request, reply) => { if (typeof request.headers.apikey !== 'string' || !apiKeys.has(request.headers.apikey)) { - reply.status(401).send() + return reply.status(401).send() } }) }, diff --git a/src/http/plugins/db.ts b/src/http/plugins/db.ts index 9bab1b612..2433255c1 100644 --- a/src/http/plugins/db.ts +++ b/src/http/plugins/db.ts @@ -1,13 +1,10 @@ -import fastifyPlugin from 'fastify-plugin' -import { getConfig, MultitenantMigrationStrategy } from '../../config' +import { createMutexByKey } from '@internal/concurrency' import { + getPostgresConnection, getServiceKeyUser, getTenantConfig, TenantConnection, - getPostgresConnection, } from '@internal/database' -import { logSchema } from '@internal/monitoring' -import { createMutexByKey } from '@internal/concurrency' import { areMigrationsUpToDate, DBMigration, @@ -17,6 +14,9 @@ import { updateTenantMigrationsState, } from '@internal/database/migrations' import { ERRORS } from '@internal/errors' +import { logSchema } from '@internal/monitoring' +import fastifyPlugin from 'fastify-plugin' +import { getConfig, MultitenantMigrationStrategy } from '../../config' declare module 'fastify' { interface FastifyRequest { @@ -25,7 +25,8 @@ declare module 'fastify' { } } -const { dbMigrationStrategy, isMultitenant, dbMigrationFreezeAt } = getConfig() +const { databaseEnableQueryCancellation, dbMigrationStrategy, isMultitenant, dbMigrationFreezeAt } = + getConfig() export const db = fastifyPlugin( async function db(fastify) { @@ -54,6 +55,11 @@ export const db = fastifyPlugin( method: request.method, operation: () => request.operation?.type, }) + + // Connect abort signal to DB connection for query cancellation + if (request.signals?.disconnect?.signal && databaseEnableQueryCancellation) { + request.db.setAbortSignal(request.signals.disconnect.signal) + } }) fastify.addHook('onSend', async (request, reply, payload) => { @@ -118,6 +124,11 @@ export const dbSuperUser = fastifyPlugin( maxConnections: opts.maxConnections, operation: () => request.operation?.type, }) + + // Connect abort signal to DB connection for query cancellation + if (request.signals?.disconnect?.signal && databaseEnableQueryCancellation) { + request.db.setAbortSignal(request.signals.disconnect.signal) + } }) fastify.addHook('onSend', async (request, reply, payload) => { diff --git a/src/http/plugins/empty-json-body.ts b/src/http/plugins/empty-json-body.ts new file mode 100644 index 000000000..00516da25 --- /dev/null +++ b/src/http/plugins/empty-json-body.ts @@ -0,0 +1,19 @@ +import { FastifyInstance } from 'fastify' + +export function registerJsonParserAllowingEmptyBody(fastify: FastifyInstance) { + const defaultJsonParser = fastify.getDefaultJsonParser( + fastify.initialConfig.onProtoPoisoning ?? 'error', + fastify.initialConfig.onConstructorPoisoning ?? 'error' + ) + + fastify.addContentTypeParser('application/json', { parseAs: 'string' }, (request, body, done) => { + if (!body) { + done(null, null) + return + } + + const jsonBody = typeof body === 'string' ? body : body.toString('utf8') + + defaultJsonParser(request, jsonBody, done) + }) +} diff --git a/src/http/plugins/header-validator.test.ts b/src/http/plugins/header-validator.test.ts new file mode 100644 index 000000000..c454c2b1a --- /dev/null +++ b/src/http/plugins/header-validator.test.ts @@ -0,0 +1,111 @@ +import { StorageBackendError } from '@internal/errors' +import Fastify, { FastifyInstance } from 'fastify' +import { setErrorHandler } from '../error-handler' +import { headerValidator } from './header-validator' + +describe('header-validator plugin', () => { + let app: FastifyInstance + + beforeEach(async () => { + app = Fastify() + await app.register(headerValidator()) + setErrorHandler(app) + }) + + afterEach(async () => { + await app.close() + }) + + it('should reject response with newline in header value', async () => { + app.get('/test', async (_request, reply) => { + reply.header('x-test', 'value\nwith\nnewlines') + return { ok: true } + }) + + const response = await app.inject({ method: 'GET', url: '/test' }) + + expect(response.statusCode).toBe(400) + const body = response.json() + expect(body.message).toContain('Invalid character in response header') + expect(body.message).toContain('x-test') + }) + + it('should reject response with carriage return in header value', async () => { + app.get('/test', async (_request, reply) => { + reply.header('x-custom', 'value\rwith\rCR') + return { ok: true } + }) + + const response = await app.inject({ method: 'GET', url: '/test' }) + + expect(response.statusCode).toBe(400) + const body = response.json() + expect(body.error).toBe('Bad Request') + expect(body.message).toContain('Invalid character in response header') + }) + + it('should allow valid header values with TAB character', async () => { + app.get('/test', async (_request, reply) => { + reply.header('x-custom', 'value\twith\ttabs') + return { ok: true } + }) + + const response = await app.inject({ method: 'GET', url: '/test' }) + + expect(response.statusCode).toBe(200) + expect(response.headers['x-custom']).toBe('value\twith\ttabs') + }) + + it('should allow normal ASCII header values', async () => { + app.get('/test', async (_request, reply) => { + reply.header('x-transformations', 'width:100,height:200,resize:cover') + return { ok: true } + }) + + const response = await app.inject({ method: 'GET', url: '/test' }) + + expect(response.statusCode).toBe(200) + expect(response.headers['x-transformations']).toBe('width:100,height:200,resize:cover') + }) + + it('should reject response with newline in array header value', async () => { + app.get('/test', async (_request, reply) => { + reply.header('x-test', ['blah', 'stuff', 'value\nwith\nnewlines', 'other']) + return { ok: true } + }) + + const response = await app.inject({ method: 'GET', url: '/test' }) + + expect(response.statusCode).toBe(400) + const body = response.json() + expect(body.message).toContain('Invalid character in response header') + expect(body.message).toContain('x-test') + }) + + it('should allow normal ASCII array header values', async () => { + app.get('/test', async (_request, reply) => { + reply.header('x-transformations', ['width:100,height:200,resize:cover', 'blah', 'blah']) + return { ok: true } + }) + + const response = await app.inject({ method: 'GET', url: '/test' }) + + expect(response.statusCode).toBe(200) + expect(response.headers['x-transformations']).toEqual([ + 'width:100,height:200,resize:cover', + 'blah', + 'blah', + ]) + }) + + it('should close the connection when a renderable error requests it', async () => { + app.get('/test-close-connection', async () => { + throw StorageBackendError.fromError(new Error('socket hang up')).withConnectionClose() + }) + + const response = await app.inject({ method: 'GET', url: '/test-close-connection' }) + + expect(response.statusCode).toBe(500) + expect(response.headers.connection).toBe('close') + }) +}) diff --git a/src/http/plugins/header-validator.ts b/src/http/plugins/header-validator.ts new file mode 100644 index 000000000..6e7f9e8be --- /dev/null +++ b/src/http/plugins/header-validator.ts @@ -0,0 +1,52 @@ +import { ERRORS } from '@internal/errors' +import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify' +import fastifyPlugin from 'fastify-plugin' + +/** + * Matches invalid HTTP header characters per RFC 7230 field-vchar specification. + * Valid: TAB (0x09), visible ASCII (0x20-0x7E), obs-text (0x80-0xFF). + * Invalid: control characters (0x00-0x1F except TAB) and DEL (0x7F). + * @see https://tools.ietf.org/html/rfc7230#section-3.2 + */ +const INVALID_HEADER_CHAR_PATTERN = /[^\t\x20-\x7e\x80-\xff]/ + +interface HeaderValidatorOptions { + excludeUrls?: string[] +} + +/** + * Validates response headers before they're sent to prevent ERR_INVALID_CHAR crashes. + * + * Node.js throws ERR_INVALID_CHAR during writeHead() if headers contain control characters. + * This hook validates headers in onSend (before writeHead) and throws InvalidHeaderChar error + */ +export const headerValidator = (options: HeaderValidatorOptions = {}) => + fastifyPlugin( + async function headerValidatorPlugin(fastify: FastifyInstance) { + fastify.addHook('onSend', async (request: FastifyRequest, reply: FastifyReply, payload) => { + if (options.excludeUrls?.includes(request.url.toLowerCase())) { + return payload + } + + const headers = reply.getHeaders() + for (const key in headers) { + if (!Object.prototype.hasOwnProperty.call(headers, key)) { + continue + } + const value = headers[key] + if (typeof value === 'string' && INVALID_HEADER_CHAR_PATTERN.test(value)) { + throw ERRORS.InvalidHeaderChar(key, value) + } else if (Array.isArray(value)) { + for (const item of value) { + if (typeof item === 'string' && INVALID_HEADER_CHAR_PATTERN.test(item)) { + throw ERRORS.InvalidHeaderChar(key, item) + } + } + } + } + + return payload + }) + }, + { name: 'header-validator' } + ) diff --git a/src/http/plugins/iceberg.ts b/src/http/plugins/iceberg.ts index 9c999d14f..0a7eff123 100644 --- a/src/http/plugins/iceberg.ts +++ b/src/http/plugins/iceberg.ts @@ -1,10 +1,11 @@ -import fastifyPlugin from 'fastify-plugin' -import { FastifyInstance } from 'fastify' -import { KnexMetastore, TableIndex } from '@storage/protocols/iceberg/knex' import { getTenantConfig, multitenantKnex } from '@internal/database' +import { KnexShardStoreFactory, ShardCatalog, SingleShard } from '@internal/sharding' +import { ICEBERG_BUCKET_RESERVED_SUFFIX } from '@storage/limits' import { getCatalogAuthStrategy, TenantAwareRestCatalog } from '@storage/protocols/iceberg/catalog' +import { KnexMetastore, TableIndex } from '@storage/protocols/iceberg/knex' +import { FastifyInstance } from 'fastify' +import fastifyPlugin from 'fastify-plugin' import { getConfig } from '../../config' -import { ICEBERG_BUCKET_RESERVED_SUFFIX } from '@storage/limits' declare module 'fastify' { interface FastifyRequest { @@ -46,10 +47,15 @@ export const icebergRestCatalog = fastifyPlugin(async function (fastify: Fastify req.icebergCatalog = new TenantAwareRestCatalog({ tenantId: req.tenantId, - limits: limits, + limits, restCatalogUrl: icebergCatalogUrl, - warehouse: icebergWarehouse, auth: catalogAuthType, + sharding: isMultitenant + ? new ShardCatalog(new KnexShardStoreFactory(multitenantKnex)) + : new SingleShard({ + shardKey: icebergWarehouse, + capacity: 10000, + }), metastore: new KnexMetastore(isMultitenant ? multitenantKnex : req.db.pool.acquire(), { multiTenant: isMultitenant, schema: isMultitenant ? 'public' : 'storage', diff --git a/src/http/plugins/index.ts b/src/http/plugins/index.ts index d8e02b9bf..436b2ece7 100644 --- a/src/http/plugins/index.ts +++ b/src/http/plugins/index.ts @@ -1,13 +1,16 @@ export * from './apikey' +export * from './db' +export * from './empty-json-body' +export * from './header-validator' +export * from './iceberg' export * from './jwt' export * from './log-request' -export * from './db' -export * from './storage' -export * from './tenant-id' -export * from './tenant-feature' export * from './metrics' -export * from './xml' +export * from './signals' export * from './signature-v4' +export * from './storage' +export * from './tenant-feature' +export * from './tenant-id' export * from './tracing' -export * from './signals' -export * from './iceberg' +export * from './vector' +export * from './xml' diff --git a/src/http/plugins/jwt.ts b/src/http/plugins/jwt.ts index 0ef11a402..f5577cd0e 100644 --- a/src/http/plugins/jwt.ts +++ b/src/http/plugins/jwt.ts @@ -1,9 +1,8 @@ -import fastifyPlugin from 'fastify-plugin' -import { JWTPayload } from 'jose' - -import { verifyJWTWithCache, verifyJWT } from '@internal/auth' +import { verifyJWT, verifyJWTWithCache } from '@internal/auth' import { getJwtSecret } from '@internal/database' import { ERRORS } from '@internal/errors' +import fastifyPlugin from 'fastify-plugin' +import { JWTPayload } from 'jose' import { getConfig } from '../../config' declare module 'fastify' { @@ -21,6 +20,7 @@ declare module 'fastify' { interface JWTPluginOptions { enforceJwtRoles?: string[] + skipIfAlreadyAuthenticated?: boolean } const { jwtCachingEnabled } = getConfig() @@ -33,6 +33,10 @@ export const jwt = fastifyPlugin( fastify.decorateRequest('jwtPayload', undefined) fastify.addHook('preHandler', async (request) => { + if (opts.skipIfAlreadyAuthenticated && request.isAuthenticated && request.jwtPayload) { + return + } + request.jwt = (request.headers.authorization || '').replace(BEARER, '') if (!request.jwt && request.routeOptions.config.allowInvalidJwt) { diff --git a/src/http/plugins/log-request.test.ts b/src/http/plugins/log-request.test.ts new file mode 100644 index 000000000..be4849add --- /dev/null +++ b/src/http/plugins/log-request.test.ts @@ -0,0 +1,59 @@ +import Fastify, { FastifyInstance } from 'fastify' +import { logRequest } from './log-request' + +describe('log-request plugin', () => { + let app: FastifyInstance + + beforeEach(async () => { + app = Fastify() + await app.register(logRequest({})) + }) + + afterEach(async () => { + await app.close() + }) + + it('derives resources from route params and prefixes them', async () => { + app.get('/bucket/:bucket/object/:name', async (request) => { + return { + resources: request.resources, + } + }) + + const response = await app.inject({ + method: 'GET', + url: '/bucket/demo/object/file.txt', + }) + + expect(response.statusCode).toBe(200) + expect(response.json()).toEqual({ + resources: ['/demo/file.txt'], + }) + }) + + it('prefers configured resources and normalizes missing leading slashes', async () => { + app.get( + '/bucket/:bucket', + { + config: { + resources: () => ['bucket/demo', '/object/demo'], + }, + }, + async (request) => { + return { + resources: request.resources, + } + } + ) + + const response = await app.inject({ + method: 'GET', + url: '/bucket/demo', + }) + + expect(response.statusCode).toBe(200) + expect(response.json()).toEqual({ + resources: ['/bucket/demo', '/object/demo'], + }) + }) +}) diff --git a/src/http/plugins/log-request.ts b/src/http/plugins/log-request.ts index b596a5434..56e8e7a69 100644 --- a/src/http/plugins/log-request.ts +++ b/src/http/plugins/log-request.ts @@ -1,8 +1,7 @@ -import fastifyPlugin from 'fastify-plugin' -import { logSchema, redactQueryParamFromRequest } from '@internal/monitoring' -import { trace } from '@opentelemetry/api' -import { FastifyRequest } from 'fastify/types/request' +import { logger, logSchema, redactQueryParamFromRequest } from '@internal/monitoring' import { FastifyReply } from 'fastify/types/reply' +import { FastifyRequest } from 'fastify/types/request' +import fastifyPlugin from 'fastify-plugin' interface RequestLoggerOptions { excludeUrls?: string[] @@ -14,11 +13,13 @@ declare module 'fastify' { operation?: { type: string } resources?: string[] startTime: number + executionTime?: number } interface FastifyContextConfig { operation?: { type: string } resources?: (req: FastifyRequest) => string[] + logMetadata?: (req: FastifyRequest) => Record } } @@ -56,36 +57,69 @@ export const logRequest = (options: RequestLoggerOptions) => * Adds req.resources and req.operation to the request object */ fastify.addHook('preHandler', async (req) => { - const resourceFromParams = Object.values(req.params || {}).join('/') - const resources = getFirstDefined( - req.resources, - req.routeOptions.config.resources?.(req), - (req.raw as any).resources, - resourceFromParams ? [resourceFromParams] : ([] as string[]) - ) + let resources = req.resources + + if (resources === undefined) { + resources = req.routeOptions.config.resources?.(req) + } + + if (resources === undefined) { + resources = (req.raw as any).resources + } + + if (resources === undefined) { + const params = req.params as Record | undefined + let resourceFromParams = '' + + if (params) { + let first = true + for (const key in params) { + if (!Object.prototype.hasOwnProperty.call(params, key)) { + continue + } + + if (!first) { + resourceFromParams += '/' + } + + const value = params[key] + resourceFromParams += value == null ? '' : String(value) + first = false + } + } + + resources = resourceFromParams ? [resourceFromParams] : [] + } if (resources && resources.length > 0) { - resources.map((resource, index) => { + for (let index = 0; index < resources.length; index++) { + const resource = resources[index] if (!resource.startsWith('/')) { resources[index] = `/${resource}` } - }) + } } req.resources = resources req.operation = req.routeOptions.config.operation - if (req.operation) { - trace.getActiveSpan()?.setAttribute('http.operation', req.operation.type) + if (req.operation && typeof req.opentelemetry === 'function') { + req.opentelemetry()?.span?.setAttribute('http.operation', req.operation.type) } }) + fastify.addHook('onSend', async (req, _, payload) => { + req.executionTime = Date.now() - req.startTime + return payload + }) + fastify.addHook('onResponse', async (req, reply) => { doRequestLog(req, { reply, excludeUrls: options.excludeUrls, statusCode: reply.statusCode, responseTime: reply.elapsedTime, + executionTime: req.executionTime, }) }) }, @@ -97,6 +131,7 @@ interface LogRequestOptions { excludeUrls?: string[] statusCode: number | 'ABORTED REQ' | 'ABORTED RES' responseTime: number + executionTime?: number } function doRequestLog(req: FastifyRequest, options: LogRequestOptions) { @@ -118,14 +153,43 @@ function doRequestLog(req: FastifyRequest, options: LogRequestOptions) { const error = (req.raw as any).executionError || req.executionError const tenantId = req.tenantId + let reqMetadata: Record = {} + + if (req.routeOptions.config.logMetadata) { + try { + reqMetadata = req.routeOptions.config.logMetadata(req) + + if (reqMetadata) { + try { + if (typeof req.opentelemetry === 'function') { + req.opentelemetry()?.span?.setAttribute('http.metadata', JSON.stringify(reqMetadata)) + } + } catch (e) { + // do nothing + logSchema.warning(logger, 'Failed to serialize log metadata', { + type: 'otel', + error: e, + }) + } + } + } catch (e) { + logSchema.error(logger, 'Failed to get log metadata', { + type: 'request', + error: e, + }) + } + } + const buildLogMessage = `${tenantId} | ${rMeth} | ${statusCode} | ${cIP} | ${rId} | ${rUrl} | ${uAgent}` logSchema.request(req.log, buildLogMessage, { type: 'request', req, + reqMetadata, res: options.reply, responseTime: options.responseTime, - error: error, + executionTime: options.executionTime, + error, owner: req.owner, role: req.jwtPayload?.role, resources: req.resources, @@ -133,12 +197,3 @@ function doRequestLog(req: FastifyRequest, options: LogRequestOptions) { serverTimes: req.serverTimings, }) } - -function getFirstDefined(...values: any[]): T | undefined { - for (const value of values) { - if (value !== undefined) { - return value - } - } - return undefined -} diff --git a/src/http/plugins/metrics.ts b/src/http/plugins/metrics.ts index 115975cc2..f46cae0e8 100644 --- a/src/http/plugins/metrics.ts +++ b/src/http/plugins/metrics.ts @@ -1,42 +1,133 @@ +import { + httpRequestDuration, + httpRequestSizeBytes, + httpResponseSizeBytes, +} from '@internal/monitoring/metrics' +import { handleMetricsRequest } from '@internal/monitoring/otel-metrics' import fastifyPlugin from 'fastify-plugin' -import { MetricsRegistrar } from '@internal/monitoring/metrics' -import fastifyMetrics from 'fastify-metrics' import { getConfig } from '../../config' -const { region, defaultMetricsEnabled } = getConfig() +const { prometheusMetricsEnabled } = getConfig() interface MetricsOptions { enabledEndpoint?: boolean + excludeRoutes?: string[] + groupStatusCodes?: boolean } -export const metrics = ({ enabledEndpoint }: MetricsOptions) => +export const metrics = (options: MetricsOptions = {}) => + fastifyPlugin(async (fastify) => { + // Register HTTP metrics plugin + fastify.register(httpMetrics(options)) + + // Register metrics endpoint if enabled + if (prometheusMetricsEnabled) { + fastify.register(metricsEndpoint(options)) + } + }) + +export const metricsEndpoint = ({ enabledEndpoint }: MetricsOptions) => { + // Metrics endpoint plugin + return fastifyPlugin( + async (fastify) => { + if (enabledEndpoint) { + fastify.get('/metrics', handleMetricsRequest) + } + }, + { name: 'otel-metrics' } + ) +} + +interface HttpMetricsOptions { + /** Routes to exclude from metrics collection */ + excludeRoutes?: string[] + /** Whether to group status codes (2xx, 3xx, etc.) */ + groupStatusCodes?: boolean +} + +/** + * Fastify plugin for collecting HTTP request metrics. + * Records request duration (histogram) and request count (counter) + * using OpenTelemetry with tenant support. + */ +export const httpMetrics = (options: HttpMetricsOptions = {}) => fastifyPlugin( async (fastify) => { - fastify.register(fastifyMetrics, { - endpoint: enabledEndpoint ? '/metrics' : null, - defaultMetrics: { - enabled: defaultMetricsEnabled, - register: MetricsRegistrar, - prefix: 'storage_api_', - labels: { - region, - }, - }, - routeMetrics: { - enabled: defaultMetricsEnabled, - routeBlacklist: ['/metrics', '/status', '/health'], - overrides: { - summary: { - name: 'storage_api_http_request_summary_seconds', - }, - histogram: { - name: 'storage_api_http_request_duration_seconds', - }, - }, - registeredRoutesOnly: true, - groupStatusCodes: true, - }, + const excludeRoutes = options.excludeRoutes || [ + '/metrics', + '/status', + '/health', + '/healthcheck', + ] + const groupStatusCodes = options.groupStatusCodes ?? true + + // Hook into request lifecycle to measure duration + fastify.addHook('onRequest', async (request) => { + // Store start time on request for later use + request.metricsStartTime = process.hrtime.bigint() + }) + + fastify.addHook('onResponse', async (request, reply) => { + const route = request.routeOptions?.url || 'unknown' + + // Skip excluded routes (match start of path) + if (excludeRoutes.some((r) => route === r || route.startsWith(r + '/'))) { + return + } + + const startTime = request.metricsStartTime + if (!startTime) return + + // Calculate duration in seconds + const endTime = process.hrtime.bigint() + const durationNs = endTime - startTime + const durationSeconds = Number(durationNs) / 1e9 + + const method = request.method + const statusCode = groupStatusCodes + ? `${Math.floor(reply.statusCode / 100)}xx` + : String(reply.statusCode) + + const attributes = { + method, + operation: request.operation?.type || 'unknown', + status_code: statusCode, + tenantId: request.tenantId || '', + } + + // Record duration (histogram count replaces httpRequestsTotal) + httpRequestDuration.record(durationSeconds, attributes) + + // Record request size from content-length header + const requestContentLength = request.headers['content-length'] + if (requestContentLength) { + const requestSize = parseInt(requestContentLength, 10) + if (!isNaN(requestSize) && requestSize > 0) { + httpRequestSizeBytes.add(requestSize, attributes) + } + } + + // Record response size from content-length header + const responseContentLength = reply.getHeader('content-length') + if (responseContentLength) { + const responseSize = + typeof responseContentLength === 'string' + ? parseInt(responseContentLength, 10) + : typeof responseContentLength === 'number' + ? responseContentLength + : 0 + if (!isNaN(responseSize) && responseSize > 0) { + httpResponseSizeBytes.add(responseSize, attributes) + } + } }) }, - { name: 'prometheus-metrics' } + { name: 'http-metrics' } ) + +// Extend FastifyRequest to include metricsStartTime +declare module 'fastify' { + interface FastifyRequest { + metricsStartTime?: bigint + } +} diff --git a/src/http/plugins/signals.ts b/src/http/plugins/signals.ts index a47346217..857b3ca00 100644 --- a/src/http/plugins/signals.ts +++ b/src/http/plugins/signals.ts @@ -1,5 +1,5 @@ -import fastifyPlugin from 'fastify-plugin' import { FastifyInstance } from 'fastify' +import fastifyPlugin from 'fastify-plugin' declare module 'fastify' { interface FastifyRequest { diff --git a/src/http/plugins/signature-v4.ts b/src/http/plugins/signature-v4.ts index 6c7a7674a..a94661360 100644 --- a/src/http/plugins/signature-v4.ts +++ b/src/http/plugins/signature-v4.ts @@ -1,16 +1,21 @@ -import { FastifyInstance, FastifyRequest } from 'fastify' -import fastifyPlugin from 'fastify-plugin' +import { Writable } from 'node:stream' +import { MultipartFile, MultipartValue } from '@fastify/multipart' +import { isJwtToken, signJWT, verifyJWT } from '@internal/auth' import { getJwtSecret, getTenantConfig, s3CredentialsManager } from '@internal/database' -import { ClientSignature, SignatureV4 } from '@storage/protocols/s3' -import { signJWT, verifyJWT } from '@internal/auth' import { ERRORS } from '@internal/errors' - -import { getConfig } from '../../config' -import { MultipartFile, MultipartValue } from '@fastify/multipart' +import { RequestByteCounterStream } from '@internal/streams' +import { HashSpillWritable } from '@internal/streams/hash-stream' +import { ClientSignature, SignatureV4, SignatureV4Service } from '@storage/protocols/s3' +import { ByteLimitTransformStream } from '@storage/protocols/s3/byte-limit-stream' import { ChunkSignatureV4Parser, V4StreamingAlgorithm, } from '@storage/protocols/s3/signature-v4-stream' +import { FastifyInstance, FastifyRequest } from 'fastify' +import fastifyPlugin from 'fastify-plugin' +import { compose, Readable } from 'stream' +import { getConfig } from '../../config' +import { enforceJwtRole } from './jwt' const { anonKeyAsync, @@ -24,101 +29,180 @@ const { s3ProtocolAccessKeyId, s3ProtocolAccessKeySecret, s3ProtocolNonCanonicalHostHeader, + storagePublicUrl, + s3OmitPrefixFromCanonicalUri, } = getConfig() +const parsedPublicUrl = storagePublicUrl ? new URL(storagePublicUrl) : undefined + type AWSRequest = FastifyRequest<{ Querystring: { 'X-Amz-Credential'?: string } }> declare module 'fastify' { interface FastifyRequest { - multiPartFileStream?: MultipartFile streamingSignatureV4?: ChunkSignatureV4Parser + multiPartFileStream?: MultipartFile + bodySha256: string } } export const signatureV4 = fastifyPlugin( - async function (fastify: FastifyInstance) { - fastify.addHook('preHandler', async (request: AWSRequest) => { - const clientSignature = await extractSignature(request) - - const sessionToken = clientSignature.sessionToken - - const { - signature: signatureV4, - claims, - token, - } = await createServerSignature(request.tenantId, clientSignature) - - let storagePrefix = s3ProtocolPrefix - if ( - requestAllowXForwardedPrefix && - typeof request.headers['x-forwarded-prefix'] === 'string' - ) { - storagePrefix = request.headers['x-forwarded-prefix'] - } + async function ( + fastify: FastifyInstance, + opts: { + service?: SignatureV4Service + allowBodyHash?: boolean + skipIfJwtToken?: boolean + enforceJwtRoles?: string[] + } = { + service: SignatureV4Service.S3, + allowBodyHash: false, + skipIfJwtToken: false, + enforceJwtRoles: [], + } + ) { + // Use preParsing when allowing to pre-calculate the sha256 of the body + if (opts.allowBodyHash) { + fastify.addHook('preParsing', async (request: AWSRequest, reply, bodyPayload) => { + if (opts.skipIfJwtToken && isJwtToken(request.headers.authorization || '')) { + return bodyPayload + } - const isVerified = signatureV4.verify(clientSignature, { - url: request.url, - body: request.body as string | ReadableStream | Buffer, - headers: request.headers as Record, - method: request.method, - query: request.query as Record, - prefix: storagePrefix, + return await authorizeRequestSignV4( + request, + bodyPayload as Readable, + SignatureV4Service.S3VECTORS, + opts.allowBodyHash + ) }) + } - if (!isVerified && !sessionToken) { - throw ERRORS.SignatureDoesNotMatch( - 'The request signature we calculated does not match the signature you provided. Check your key and signing method.' - ) - } + // Use preHandler when not allowing to pre-calculate the sha256 of the body + if (!opts.allowBodyHash) { + fastify.addHook('preHandler', async (request: AWSRequest) => { + await authorizeRequestSignV4(request, request.raw as Readable, SignatureV4Service.S3) + }) + } - if (!isVerified && sessionToken) { - throw ERRORS.SignatureDoesNotMatch( - 'The request signature we calculated does not match the signature you provided, Check your credentials. ' + - 'The session token should be a valid JWT token' - ) - } + if (opts.enforceJwtRoles) { + fastify.register(enforceJwtRole, { + roles: opts.enforceJwtRoles, + }) + } + }, + { name: 'auth-signature-v4' } +) - const { secret: jwtSecret, jwks } = await getJwtSecret(request.tenantId) - - if (token) { - const payload = await verifyJWT(token, jwtSecret, jwks) - request.jwt = token - request.jwtPayload = payload - request.owner = payload.sub - - if (SignatureV4.isChunkedUpload(request.headers)) { - request.streamingSignatureV4 = createStreamingSignatureV4Parser({ - signatureV4, - streamAlgorithm: request.headers['x-amz-content-sha256'] as V4StreamingAlgorithm, - clientSignature, - trailers: request.headers['x-amz-trailer'] as string, - }) - } - return - } +/** + * Authorize incoming request with Signature V4 + * + * @param request + * @param body + * @param service + * @param allowBodyHash + */ +async function authorizeRequestSignV4( + request: AWSRequest, + body: string | Buffer | Readable, + service: SignatureV4Service, + allowBodyHash = false +) { + const clientSignature = await extractSignature(request) + + const sessionToken = clientSignature.sessionToken + + const { + signature: signatureV4, + claims, + token, + } = await createServerSignature(request.tenantId, clientSignature, service, allowBodyHash) + + let storagePrefix = s3ProtocolPrefix + if (requestAllowXForwardedPrefix && typeof request.headers['x-forwarded-prefix'] === 'string') { + storagePrefix = request.headers['x-forwarded-prefix'] + } - if (!claims) { - throw ERRORS.AccessDenied('Missing claims') - } + let hashStreamComposer: (Writable & { digestHex: () => string }) | undefined + let byteHasherStream: + | (Writable & { + digestHex: () => string + toReadable: (opts: { autoCleanup: boolean }) => Readable + }) + | undefined - const jwt = await signJWT(claims, jwtSecret, '5m') + if (allowBodyHash) { + byteHasherStream = new HashSpillWritable({ + alg: 'sha256', + limitInMemoryBytes: 1024 * 1024 * 5, // 5MB + }) + hashStreamComposer = compose(new ByteLimitTransformStream(1024 * 1024 * 20), byteHasherStream) + hashStreamComposer!.digestHex = byteHasherStream.digestHex.bind(byteHasherStream) + } - request.jwt = jwt - request.jwtPayload = claims - request.owner = claims.sub + const isVerified = await signatureV4.verify(clientSignature, { + url: request.url, + body, + headers: request.headers as Record, + method: request.method, + query: request.query as Record, + prefix: storagePrefix, + payloadHasher: hashStreamComposer, + }) - if (SignatureV4.isChunkedUpload(request.headers)) { - request.streamingSignatureV4 = createStreamingSignatureV4Parser({ - signatureV4, - streamAlgorithm: request.headers['x-amz-content-sha256'] as V4StreamingAlgorithm, - clientSignature, - trailers: request.headers['x-amz-trailer'] as string, - }) - } + if (!isVerified && !sessionToken) { + throw ERRORS.SignatureDoesNotMatch( + 'The request signature we calculated does not match the signature you provided. Check your key and signing method.' + ) + } + + if (!isVerified && sessionToken) { + throw ERRORS.SignatureDoesNotMatch( + 'The request signature we calculated does not match the signature you provided, Check your credentials. ' + + 'The session token should be a valid JWT token' + ) + } + + const wasBodyHashed = allowBodyHash && byteHasherStream && byteHasherStream.writableEnded + + const returnStream = wasBodyHashed + ? byteHasherStream!.toReadable({ autoCleanup: true }) + : (body as Readable) + + const { secret: jwtSecret, jwks } = await getJwtSecret(request.tenantId) + + if (!token) { + if (!claims) { + throw ERRORS.AccessDenied('Missing claims') + } + + const jwt = await signJWT(claims, jwtSecret, '5m') + + request.isAuthenticated = true + request.jwt = jwt + request.jwtPayload = claims + request.owner = claims.sub + } else { + const payload = await verifyJWT(token, jwtSecret, jwks) + request.isAuthenticated = true + request.jwt = token + request.jwtPayload = payload + request.owner = payload.sub + } + + if (SignatureV4.isChunkedUpload(request.headers)) { + request.streamingSignatureV4 = createStreamingSignatureV4Parser({ + signatureV4, + streamAlgorithm: request.headers['x-amz-content-sha256'] as V4StreamingAlgorithm, + clientSignature, + trailers: request.headers['x-amz-trailer'] as string, }) - }, - { name: 'auth-signature-v4' } -) + } + + if (wasBodyHashed) { + return compose(returnStream, new RequestByteCounterStream()) + } + + return returnStream +} async function extractSignature(req: AWSRequest) { if (typeof req.headers.authorization === 'string') { @@ -156,9 +240,13 @@ async function extractSignature(req: AWSRequest) { throw ERRORS.AccessDenied('Missing signature') } -async function createServerSignature(tenantId: string, clientSignature: ClientSignature) { +async function createServerSignature( + tenantId: string, + clientSignature: ClientSignature, + awsService = SignatureV4Service.S3, + allowBodyHash = false +) { const awsRegion = storageS3Region - const awsService = 's3' if (clientSignature?.sessionToken) { const tenantAnonKey = isMultitenant @@ -172,7 +260,10 @@ async function createServerSignature(tenantId: string, clientSignature: ClientSi const signature = new SignatureV4({ enforceRegion: s3ProtocolEnforceRegion, allowForwardedHeader: s3ProtocolAllowForwardedHeader, + allowBodyHashing: allowBodyHash, nonCanonicalForwardedHost: s3ProtocolNonCanonicalHostHeader, + publicUrl: parsedPublicUrl, + s3OmitPrefixFromCanonicalUri: s3OmitPrefixFromCanonicalUri, credentials: { accessKey: tenantId, secretKey: tenantAnonKey, @@ -193,7 +284,10 @@ async function createServerSignature(tenantId: string, clientSignature: ClientSi const signature = new SignatureV4({ enforceRegion: s3ProtocolEnforceRegion, allowForwardedHeader: s3ProtocolAllowForwardedHeader, + allowBodyHashing: allowBodyHash, nonCanonicalForwardedHost: s3ProtocolNonCanonicalHostHeader, + publicUrl: parsedPublicUrl, + s3OmitPrefixFromCanonicalUri: s3OmitPrefixFromCanonicalUri, credentials: { accessKey: credential.accessKey, secretKey: credential.secretKey, @@ -214,7 +308,10 @@ async function createServerSignature(tenantId: string, clientSignature: ClientSi const signature = new SignatureV4({ enforceRegion: s3ProtocolEnforceRegion, allowForwardedHeader: s3ProtocolAllowForwardedHeader, + allowBodyHashing: allowBodyHash, nonCanonicalForwardedHost: s3ProtocolNonCanonicalHostHeader, + publicUrl: parsedPublicUrl, + s3OmitPrefixFromCanonicalUri: s3OmitPrefixFromCanonicalUri, credentials: { accessKey: s3ProtocolAccessKeyId, secretKey: s3ProtocolAccessKeySecret, diff --git a/src/http/plugins/storage.ts b/src/http/plugins/storage.ts index fde17564f..18f4d5967 100644 --- a/src/http/plugins/storage.ts +++ b/src/http/plugins/storage.ts @@ -1,10 +1,10 @@ -import fastifyPlugin from 'fastify-plugin' -import { StorageBackendAdapter, createStorageBackend } from '@storage/backend' -import { Storage } from '@storage/storage' -import { StorageKnexDB } from '@storage/database' -import { getConfig } from '../../config' +import { createStorageBackend, StorageBackendAdapter } from '@storage/backend' import { CdnCacheManager } from '@storage/cdn/cdn-cache-manager' +import { StorageKnexDB } from '@storage/database' import { PassThroughLocation, TenantLocation } from '@storage/locator' +import { Storage } from '@storage/storage' +import fastifyPlugin from 'fastify-plugin' +import { getConfig } from '../../config' declare module 'fastify' { interface FastifyRequest { diff --git a/src/http/plugins/tenant-feature.ts b/src/http/plugins/tenant-feature.ts index 4d8ee9448..f25b48c3d 100644 --- a/src/http/plugins/tenant-feature.ts +++ b/src/http/plugins/tenant-feature.ts @@ -1,5 +1,5 @@ -import fastifyPlugin from 'fastify-plugin' import { Features, tenantHasFeature } from '@internal/database' +import fastifyPlugin from 'fastify-plugin' import { getConfig } from '../../config' @@ -20,7 +20,7 @@ export const requireTenantFeature = (feature: keyof Features) => const hasFeature = await tenantHasFeature(request.tenantId, feature) if (!hasFeature) { - reply.status(403).send({ + return reply.status(403).send({ error: 'FeatureNotEnabled', statusCode: '403', message: 'feature not enabled for this tenant', diff --git a/src/http/plugins/tracing.ts b/src/http/plugins/tracing.ts index a3859c739..e44b4c5bb 100644 --- a/src/http/plugins/tracing.ts +++ b/src/http/plugins/tracing.ts @@ -1,12 +1,7 @@ -import fastifyPlugin from 'fastify-plugin' -import { isIP } from 'net' import { getTenantConfig } from '@internal/database' - +import { logSchema } from '@internal/monitoring' +import fastifyPlugin from 'fastify-plugin' import { getConfig } from '../../config' -import { context, trace } from '@opentelemetry/api' -import { Span, traceCollector } from '@internal/monitoring/otel-processor' -import { ReadableSpan } from '@opentelemetry/sdk-trace-base' -import { logger, logSchema } from '@internal/monitoring' declare module 'fastify' { interface FastifyRequest { @@ -15,23 +10,13 @@ declare module 'fastify' { } } -const { - isMultitenant, - tracingEnabled, - tracingMode: defaultTracingMode, - tracingReturnServerTimings, - isProduction, - tracingTimeMinDuration, -} = getConfig() - -const enableLogTraces = ['debug', 'logs'].includes(defaultTracingMode || '') +const { isMultitenant, tracingEnabled, tracingMode: defaultTracingMode } = getConfig() export const tracing = fastifyPlugin( async function tracingMode(fastify) { if (!tracingEnabled) { return } - fastify.register(traceServerTime) fastify.addHook('onRequest', async (request) => { try { @@ -42,16 +27,16 @@ export const tracing = fastifyPlugin( request.tracingMode = defaultTracingMode } - if (!enableLogTraces) { - return - } - - const span = trace.getSpan(context.active()) - + // Use request.opentelemetry().span to get the root request span, + // not trace.getActiveSpan() which returns a child hook span. + const span = + typeof request.opentelemetry === 'function' ? request.opentelemetry()?.span : undefined if (span) { - // We collect logs only in full,logs,debug mode - if (request.tracingMode && !['debug'].includes(request.tracingMode)) { - traceCollector.clearTrace(span.spanContext().traceId) + if (request.tenantId) { + span.setAttribute('tenant.ref', request.tenantId) + } + if (request.tracingMode) { + span.setAttribute('trace.mode', request.tracingMode) } } } catch (e) { @@ -61,169 +46,3 @@ export const tracing = fastifyPlugin( }, { name: 'tracing-mode' } ) - -export const traceServerTime = fastifyPlugin( - async function traceServerTime(fastify) { - if (!tracingEnabled || !enableLogTraces) { - return - } - fastify.addHook('onRequest', async (req, res) => { - // Request was aborted before the server finishes to return a response - res.raw.once('close', () => { - const aborted = !res.raw.writableFinished - if (aborted) { - try { - const span = trace.getSpan(context.active()) - const traceId = span?.spanContext().traceId - - span?.setAttribute('res_aborted', true) - - if (traceId) { - const spans = traceCollector.getSpansForTrace(traceId) - if (spans) { - req.serverTimings = spansToServerTimings(spans, true) - } - traceCollector.clearTrace(traceId) - } - } catch (e) { - logSchema.error(logger, 'failed parsing server times on abort', { - error: e, - type: 'otel', - }) - } - } - }) - }) - - fastify.addHook('onResponse', async (request, reply) => { - const traceId = trace.getSpan(context.active())?.spanContext().traceId - - if (request.tracingMode !== 'debug') { - if (traceId) traceCollector.clearTrace(traceId) - return - } - - try { - if (traceId) { - const spans = traceCollector.getSpansForTrace(traceId) - if (spans) { - const serverTimingHeaders = spansToServerTimings(spans, reply.statusCode >= 500) - - request.serverTimings = serverTimingHeaders - - // Return Server-Timing if enabled - if (tracingReturnServerTimings) { - const httpServerTimes = serverTimingHeaders - .flatMap((span) => { - return [span, ...span.children] - }) - .map(({ spanName, duration }) => { - return `${spanName};dur=${duration.toFixed(3)}` // Convert to milliseconds - }) - .join(',') - reply.header('Server-Timing', httpServerTimes) - } - } - } - } catch (e) { - logSchema.error(request.log, 'failed tracing on response', { error: e, type: 'tracing' }) - } finally { - if (traceId) { - traceCollector.clearTrace(traceId) - } - } - }) - - fastify.addHook('onRequestAbort', async (req) => { - const span = trace.getSpan(context.active()) - const traceId = span?.spanContext().traceId - - if (req.tracingMode !== 'debug') { - if (traceId) traceCollector.clearTrace(traceId) - return - } - - try { - span?.setAttribute('req_aborted', true) - - if (traceId) { - const spans = traceCollector.getSpansForTrace(traceId) - if (spans) { - req.serverTimings = spansToServerTimings(spans, true) - } - } - } catch (e) { - logSchema.error(logger, 'failed parsing server times on abort', { error: e, type: 'otel' }) - } finally { - if (traceId) { - traceCollector.clearTrace(traceId) - } - } - }) - }, - { name: 'tracing-server-times' } -) - -function enrichSpanName(spanName: string, span: ReadableSpan) { - if (span.attributes['knex.version']) { - const queryOperation = (span.attributes['db.operation'] as string)?.split(' ').shift() - return ( - `pg_query_` + - queryOperation?.toUpperCase() + - (span.attributes['db.sql.table'] ? '_' + span.attributes['db.sql.table'] : '_postgres') - ) - } - - if (['GET', 'PUT', 'HEAD', 'DELETE', 'POST'].includes(spanName)) { - return `HTTP_${spanName}` - } - - return spanName -} - -function spansToServerTimings( - spans: Span[], - includeChildren = false -): { spanName: string; duration: number; action?: any; host?: string; children: any[] }[] { - const minLatency = isProduction ? tracingTimeMinDuration : 50 - - return spans.flatMap((span) => { - const duration = Math.max(span.item.duration[1], span.item.duration[0]) / 1e6 // Convert nanoseconds to milliseconds - - if (duration >= minLatency || includeChildren) { - let spanName = - span.item.name - .split('->') - .pop() - ?.trimStart() - .replaceAll('\n', '') - .replaceAll(' ', '_') - .replaceAll('-', '_') - .replaceAll('___', '_') - .replaceAll(':', '_') - .replaceAll('_undefined', '') || 'UNKNOWN' - - spanName = enrichSpanName(spanName, span.item) - const hostName = span.item.attributes['net.peer.name'] as string | undefined - - return [ - { - spanName, - duration, - action: span.item.attributes['db.statement'], - error: span.item.attributes.error, - status: span.item.status, - host: hostName - ? isIP(hostName) - ? hostName - : hostName?.split('.').slice(-3).join('.') - : undefined, - children: spansToServerTimings(span.children, true), - }, - ] - } else { - // If the span doesn't meet the minimum latency, only return its children - return spansToServerTimings(span.children) - } - }) -} diff --git a/src/http/plugins/vector.ts b/src/http/plugins/vector.ts new file mode 100644 index 000000000..5997bf0c5 --- /dev/null +++ b/src/http/plugins/vector.ts @@ -0,0 +1,56 @@ +import { getTenantConfig, multitenantKnex } from '@internal/database' +import { ERRORS } from '@internal/errors' +import { KnexShardStoreFactory, ShardCatalog, SingleShard } from '@internal/sharding' +import { + createS3VectorClient, + KnexVectorMetadataDB, + S3Vector, + VectorStoreManager, +} from '@storage/protocols/vector' +import { FastifyInstance } from 'fastify' +import fastifyPlugin from 'fastify-plugin' +import { getConfig } from '../../config' + +declare module 'fastify' { + interface FastifyRequest { + s3Vector: VectorStoreManager + } +} + +const s3VectorClient = createS3VectorClient() +const s3VectorAdapter = new S3Vector(s3VectorClient) + +export const s3vector = fastifyPlugin(async function (fastify: FastifyInstance) { + fastify.addHook('preHandler', async (req) => { + const { isMultitenant, vectorS3Buckets, vectorMaxBucketsCount, vectorMaxIndexesCount } = + getConfig() + + if (!vectorS3Buckets || vectorS3Buckets.length === 0) { + throw ERRORS.FeatureNotEnabled('vector', 'Vector service not configured') + } + + let maxBucketCount = vectorMaxBucketsCount + let maxIndexCount = vectorMaxIndexesCount + + if (isMultitenant) { + const { features } = await getTenantConfig(req.tenantId) + maxBucketCount = features?.vectorBuckets?.maxBuckets || vectorMaxBucketsCount + maxIndexCount = features?.vectorBuckets?.maxIndexes || vectorMaxIndexesCount + } + + const db = req.db.pool.acquire() + const store = new KnexVectorMetadataDB(db) + const shard = isMultitenant + ? new ShardCatalog(new KnexShardStoreFactory(multitenantKnex)) + : new SingleShard({ + shardKey: vectorS3Buckets[0], + capacity: 10000, + }) + + req.s3Vector = new VectorStoreManager(s3VectorAdapter, store, shard, { + tenantId: req.tenantId, + maxBucketCount, + maxIndexCount, + }) + }) +}) diff --git a/src/http/plugins/xml.test.ts b/src/http/plugins/xml.test.ts new file mode 100644 index 000000000..7526bacc4 --- /dev/null +++ b/src/http/plugins/xml.test.ts @@ -0,0 +1,95 @@ +import fastify, { FastifyInstance } from 'fastify' +import { xmlParser } from './xml' + +async function buildXmlApp(parseAsArray: string[] = []): Promise { + const app = fastify() + + await app.register(xmlParser, { parseAsArray }) + + app.post('/xml', async (req) => { + return { body: req.body } + }) + + app.get('/xml', async () => { + return { + ListBucketResult: { + Name: 'test-bucket', + }, + } + }) + + return app +} + +describe('xmlParser plugin', () => { + it('parses XML bodies and enforces configured array paths', async () => { + const app = await buildXmlApp(['CompleteMultipartUpload.Part']) + + try { + const response = await app.inject({ + method: 'POST', + url: '/xml', + headers: { + 'content-type': 'application/xml', + accept: 'application/json', + }, + payload: + '1etag-1', + }) + + expect(response.statusCode).toBe(200) + expect(response.json()).toEqual({ + body: { + CompleteMultipartUpload: { + Part: [{ PartNumber: 1, ETag: 'etag-1' }], + }, + }, + }) + } finally { + await app.close() + } + }) + + it('returns 400 for malformed XML payloads', async () => { + const app = await buildXmlApp() + + try { + const response = await app.inject({ + method: 'POST', + url: '/xml', + headers: { + 'content-type': 'application/xml', + accept: 'application/json', + }, + payload: '', + }) + + expect(response.statusCode).toBe(400) + expect(response.json().message).toContain('Invalid XML payload') + } finally { + await app.close() + } + }) + + it('serializes response payloads as XML when requested', async () => { + const app = await buildXmlApp() + + try { + const response = await app.inject({ + method: 'GET', + url: '/xml', + headers: { + accept: 'application/xml', + }, + }) + + expect(response.statusCode).toBe(200) + expect(response.headers['content-type']).toContain('application/xml') + expect(response.payload).toContain( + 'test-bucket' + ) + } finally { + await app.close() + } + }) +}) diff --git a/src/http/plugins/xml.ts b/src/http/plugins/xml.ts index 6d4741611..0101d925d 100644 --- a/src/http/plugins/xml.ts +++ b/src/http/plugins/xml.ts @@ -1,27 +1,86 @@ -import { FastifyInstance } from 'fastify' import accepts from '@fastify/accepts' +import { FastifyInstance } from 'fastify' import fastifyPlugin from 'fastify-plugin' import xml from 'xml2js' -// no types exists for this package -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import xmlBodyParser from 'fastify-xml-body-parser' +type XmlParserOptions = { disableContentParser?: boolean; parseAsArray?: string[] } +type RequestError = Error & { statusCode?: number } + +function forcePathAsArray(node: unknown, pathSegments: string[]): void { + if (pathSegments.length === 0 || node === null || node === undefined) { + return + } + + if (Array.isArray(node)) { + node.forEach((item) => forcePathAsArray(item, pathSegments)) + return + } + + if (typeof node !== 'object') { + return + } + + const [current, ...rest] = pathSegments + const currentRecord = node as Record + + if (!(current in currentRecord)) { + return + } + + if (rest.length === 0) { + const value = currentRecord[current] + if (value !== undefined && !Array.isArray(value)) { + currentRecord[current] = [value] + } + return + } + + forcePathAsArray(currentRecord[current], rest) +} export const xmlParser = fastifyPlugin( - async function ( - fastify: FastifyInstance, - opts: { disableContentParser?: boolean; parseAsArray?: string[] } - ) { + async function (fastify: FastifyInstance, opts: XmlParserOptions) { fastify.register(accepts) if (!opts.disableContentParser) { - fastify.register(xmlBodyParser, { - contentType: ['text/xml', 'application/xml'], - isArray: (_: string, jpath: string) => { - return opts.parseAsArray?.includes(jpath) - }, - }) + fastify.addContentTypeParser( + ['text/xml', 'application/xml'], + { parseAs: 'string' }, + (_request, body, done) => { + if (!body) { + done(null, null) + return + } + + xml.parseString( + body, + { + explicitArray: false, + trim: true, + valueProcessors: [xml.processors.parseNumbers, xml.processors.parseBooleans], + }, + (err: Error | null, parsed: unknown) => { + if (err) { + const parseError: RequestError = new Error(`Invalid XML payload: ${err.message}`) + parseError.statusCode = 400 + done(parseError) + return + } + + if (parsed && opts.parseAsArray?.length) { + opts.parseAsArray.forEach((path) => { + if (!path) { + return + } + forcePathAsArray(parsed, path.split('.')) + }) + } + + done(null, parsed) + } + ) + } + ) } fastify.addHook('preSerialization', async (req, res, payload) => { diff --git a/src/test/generic-routes.test.ts b/src/http/routes-helper.test.ts similarity index 96% rename from src/test/generic-routes.test.ts rename to src/http/routes-helper.test.ts index 4d579aafd..58af6d2c3 100644 --- a/src/test/generic-routes.test.ts +++ b/src/http/routes-helper.test.ts @@ -1,6 +1,4 @@ -'use strict' - -import { createDefaultSchema, createResponse } from '../http/routes-helper' +import { createDefaultSchema, createResponse } from './routes-helper' describe('testing Generic Routes Utils', () => { describe('creating generic responses [createResponse]', () => { diff --git a/src/http/routes/admin/index.ts b/src/http/routes/admin/index.ts index bac878191..52f76b281 100644 --- a/src/http/routes/admin/index.ts +++ b/src/http/routes/admin/index.ts @@ -1,6 +1,8 @@ +export { default as jwks } from './jwks' +export { default as metricsConfig } from './metrics' export { default as migrations } from './migrations' -export { default as tenants } from './tenants' -export { default as s3Credentials } from './s3' export { default as objects } from './objects' -export { default as jwks } from './jwks' +export { default as pprof } from './pprof' export { default as queue } from './queue' +export { default as s3Credentials } from './s3' +export { default as tenants } from './tenants' diff --git a/src/http/routes/admin/jwks.ts b/src/http/routes/admin/jwks.ts index 02e5c3650..414321bc6 100644 --- a/src/http/routes/admin/jwks.ts +++ b/src/http/routes/admin/jwks.ts @@ -1,9 +1,10 @@ -import { FastifyInstance, RequestGenericInterface } from 'fastify' -import apiKey from '../../plugins/apikey' -import { jwksManager } from '@internal/database' -import { FromSchema } from 'json-schema-to-ts' import { UrlSigningJwkGenerator } from '@internal/auth/jwks/generator' +import { jwksManager } from '@internal/database' import { logSchema } from '@internal/monitoring' +import { JwksRollUrlSigningKey } from '@storage/events' +import { FastifyInstance, RequestGenericInterface } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import apiKey from '../../plugins/apikey' const addSchema = { body: { @@ -51,6 +52,12 @@ interface JwksUpdateRequestInterface extends RequestGenericInterface { } } +interface JwksRollRequestInterface extends RequestGenericInterface { + Params: { + tenantId: string + } +} + type ValidationResult = { message: string } | undefined function validateAddJwkRequest({ jwk, kind }: JwksAddRequestInterface['Body']): ValidationResult { @@ -102,7 +109,7 @@ export default async function routes(fastify: FastifyInstance) { fastify.post( '/:tenantId/jwks', - { schema: addSchema }, + { schema: { ...addSchema, tags: ['jwks'] } }, async ({ body, params }, reply) => { const validationResult = validateAddJwkRequest(body) if (validationResult?.message) { @@ -116,7 +123,7 @@ export default async function routes(fastify: FastifyInstance) { fastify.put( '/:tenantId/jwks/:kid', - { schema: updateSchema }, + { schema: { ...updateSchema, tags: ['jwks'] } }, async (request, reply) => { const { params: { tenantId, kid }, @@ -127,26 +134,47 @@ export default async function routes(fastify: FastifyInstance) { } ) - fastify.post('/jwks/generate-all-missing', async (request, reply) => { - const { running, sent } = UrlSigningJwkGenerator.getGenerationStatus() - if (running) { - return reply - .status(400) - .send(`Generate missing jwks is already running, and has sent ${sent} items so far`) + fastify.post( + '/:tenantId/jwks/url-signing/roll', + { schema: { tags: ['jwks'] } }, + async (request, reply) => { + const { tenantId } = request.params + + await JwksRollUrlSigningKey.send({ + tenantId, + tenant: { + ref: tenantId, + }, + }) + + return reply.send({ started: true }) } + ) - UrlSigningJwkGenerator.generateUrlSigningJwksOnAllTenants( - request.signals.disconnect.signal - ).catch((e) => { - logSchema.error(request.log, 'Error generating url signing jwks for all tenants', { - type: 'jwk-generator', - error: e, + fastify.post( + '/jwks/generate-all-missing', + { schema: { tags: ['jwks'] } }, + async (request, reply) => { + const { running, sent } = UrlSigningJwkGenerator.getGenerationStatus() + if (running) { + return reply + .status(400) + .send(`Generate missing jwks is already running, and has sent ${sent} items so far`) + } + + UrlSigningJwkGenerator.generateUrlSigningJwksOnAllTenants( + request.signals.disconnect.signal + ).catch((e) => { + logSchema.error(request.log, 'Error generating url signing jwks for all tenants', { + type: 'jwk-generator', + error: e, + }) }) - }) - return reply.send({ started: true }) - }) + return reply.send({ started: true }) + } + ) - fastify.get('/jwks/generate-all-missing', (request, reply) => { + fastify.get('/jwks/generate-all-missing', { schema: { tags: ['jwks'] } }, (request, reply) => { return reply.send(UrlSigningJwkGenerator.getGenerationStatus()) }) } diff --git a/src/http/routes/admin/metrics.ts b/src/http/routes/admin/metrics.ts new file mode 100644 index 000000000..8094ad384 --- /dev/null +++ b/src/http/routes/admin/metrics.ts @@ -0,0 +1,49 @@ +import { getMetricsConfig, setMetricsEnabled } from '@internal/monitoring/metrics' +import { FastifyInstance, RequestGenericInterface } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import apiKey from '../../plugins/apikey' + +const updateMetricsConfigSchema = { + body: { + type: 'object', + properties: { + metrics: { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + enabled: { type: 'boolean' }, + }, + required: ['name', 'enabled'], + }, + }, + }, + required: ['metrics'], + }, +} as const + +interface UpdateMetricsConfigRequest extends RequestGenericInterface { + Body: FromSchema +} + +export default async function routes(fastify: FastifyInstance) { + fastify.register(apiKey) + + fastify.get('/config', { schema: { tags: ['metrics'] } }, async (_request, reply) => { + return reply.send({ + metrics: getMetricsConfig(), + }) + }) + + fastify.put( + '/config', + { schema: { ...updateMetricsConfigSchema, tags: ['metrics'] } }, + async (request, reply) => { + setMetricsEnabled(request.body.metrics) + return reply.code(200).send({ + metrics: getMetricsConfig(), + }) + } + ) +} diff --git a/src/http/routes/admin/migrations.ts b/src/http/routes/admin/migrations.ts index 618b6cd8d..de9bb63f8 100644 --- a/src/http/routes/admin/migrations.ts +++ b/src/http/routes/admin/migrations.ts @@ -1,21 +1,22 @@ -import { FastifyInstance } from 'fastify' -import { Queue } from '@internal/queue' import { multitenantKnex } from '@internal/database' -import { RunMigrationsOnTenants } from '@storage/events' -import apiKey from '../../plugins/apikey' -import { getConfig } from '../../../config' import { - DBMigration, + isDBMigrationName, resetMigrationsOnTenants, runMigrationsOnAllTenants, } from '@internal/database/migrations' +import { PG_BOSS_SCHEMA, Queue } from '@internal/queue' +import { RunMigrationsOnTenants } from '@storage/events' +import { FastifyInstance } from 'fastify' +import { getConfig } from '../../../config' +import apiKey from '../../plugins/apikey' const { pgQueueEnable } = getConfig() +const migrationQueueName = RunMigrationsOnTenants.getQueueName() export default async function routes(fastify: FastifyInstance) { fastify.register(apiKey) - fastify.post('/migrate/fleet', async (req, reply) => { + fastify.post('/migrate/fleet', { schema: { tags: ['migration'] } }, async (req, reply) => { if (!pgQueueEnable) { return reply.status(400).send({ message: 'Queue is not enabled' }) } @@ -25,30 +26,27 @@ export default async function routes(fastify: FastifyInstance) { return reply.send({ message: 'Migrations scheduled' }) }) - fastify.post('/reset/fleet', async (req, reply) => { + fastify.post('/reset/fleet', { schema: { tags: ['migration'] } }, async (req, reply) => { if (!pgQueueEnable) { return reply.status(400).send({ message: 'Queue is not enabled' }) } - const { untilMigration, markCompletedTillMigration } = req.body as any + const { untilMigration, markCompletedTillMigration } = req.body as Record - if ( - typeof untilMigration !== 'string' || - !DBMigration[untilMigration as keyof typeof DBMigration] - ) { + if (!isDBMigrationName(untilMigration)) { return reply.status(400).send({ message: 'Invalid migration' }) } if ( typeof markCompletedTillMigration === 'string' && - !DBMigration[untilMigration as keyof typeof DBMigration] + !isDBMigrationName(markCompletedTillMigration) ) { return reply.status(400).send({ message: 'Invalid migration' }) } await resetMigrationsOnTenants({ - till: untilMigration as keyof typeof DBMigration, - markCompletedTillMigration: markCompletedTillMigration + till: untilMigration, + markCompletedTillMigration: isDBMigrationName(markCompletedTillMigration) ? markCompletedTillMigration : undefined, signal: req.signals.disconnect.signal, @@ -57,28 +55,28 @@ export default async function routes(fastify: FastifyInstance) { return reply.send({ message: 'Migrations scheduled' }) }) - fastify.get('/active', async (req, reply) => { + fastify.get('/active', { schema: { tags: ['migration'] } }, async (req, reply) => { if (!pgQueueEnable) { return reply.code(400).send({ message: 'Queue is not enabled' }) } const data = await multitenantKnex - .table('pgboss_v10.job') + .table(`${PG_BOSS_SCHEMA}.job`) .where('state', 'active') - .where('name', 'tenants-migrations') + .where('name', migrationQueueName) .orderBy('created_on', 'desc') .limit(2000) return reply.send(data) }) - fastify.delete('/active', async (req, reply) => { + fastify.delete('/active', { schema: { tags: ['migration'] } }, async (req, reply) => { if (!pgQueueEnable) { return reply.code(400).send({ message: 'Queue is not enabled' }) } const data = await multitenantKnex - .table('pgboss_v10.job') + .table(`${PG_BOSS_SCHEMA}.job`) .where('state', 'active') - .where('name', 'tenants-migrations') + .where('name', migrationQueueName) .orderBy('created_on', 'desc') .update({ state: 'completed' }) .limit(2000) @@ -86,7 +84,7 @@ export default async function routes(fastify: FastifyInstance) { return reply.send(data) }) - fastify.get('/progress', async (req, reply) => { + fastify.get('/progress', { schema: { tags: ['migration'] } }, async (req, reply) => { if (!pgQueueEnable) { return reply.code(400).send({ message: 'Queue is not enabled' }) } @@ -94,7 +92,7 @@ export default async function routes(fastify: FastifyInstance) { return { remaining: queueSize } }) - fastify.get('/failed', async (req, reply) => { + fastify.get('/failed', { schema: { tags: ['migration'] } }, async (req, reply) => { if (!pgQueueEnable) { return reply.code(400).send({ message: 'Queue is not enabled' }) } diff --git a/src/http/routes/admin/objects.ts b/src/http/routes/admin/objects.ts index fda720ed0..892152440 100644 --- a/src/http/routes/admin/objects.ts +++ b/src/http/routes/admin/objects.ts @@ -1,8 +1,9 @@ -import { FastifyInstance, RequestGenericInterface } from 'fastify' -import apiKey from '../../plugins/apikey' -import { dbSuperUser, storage } from '../../plugins' +import { render } from '@internal/errors' import { ObjectScanner } from '@storage/scanner/scanner' +import { FastifyInstance, RequestGenericInterface } from 'fastify' import { FastifyReply } from 'fastify/types/reply' +import { dbSuperUser, storage } from '../../plugins' +import apiKey from '../../plugins/apikey' const listOrphanedObjects = { description: 'List Orphaned Objects', @@ -80,7 +81,7 @@ export default async function routes(fastify: FastifyInstance) { fastify.get( '/:tenantId/buckets/:bucketId/orphan-objects', { - schema: listOrphanedObjects, + schema: { ...listOrphanedObjects, tags: ['object'] }, }, async (req, reply) => { const bucket = req.params.bucketId @@ -99,7 +100,7 @@ export default async function routes(fastify: FastifyInstance) { const scanner = new ObjectScanner(req.storage) const orphanObjects = scanner.listOrphaned(bucket, { signal: req.signals.disconnect.signal, - before: before, + before, keepTmpTable: Boolean(req.query.keepTmpTable), }) @@ -113,19 +114,22 @@ export default async function routes(fastify: FastifyInstance) { for await (const result of orphanObjects) { if (result.value.length > 0) { respPing.update() - reply.raw.write( - JSON.stringify({ - ...result, - event: 'data', - }) + '\n' - ) + writeNdjson(reply, { + ...result, + event: 'data', + }) } } } catch (e) { - throw e + req.log.error({ err: e, bucket }, 'list orphaned objects stream failed') + writeNdjson(reply, { + event: 'error', + error: render(e), + }) + return } finally { respPing.clear() - reply.raw.end() + endNdjson(reply) } } ) @@ -133,7 +137,7 @@ export default async function routes(fastify: FastifyInstance) { fastify.delete( '/:tenantId/buckets/:bucketId/orphan-objects', { - schema: syncOrphanedObjects, + schema: { ...syncOrphanedObjects, tags: ['object'] }, }, async (req, reply) => { if (!req.body.deleteDbKeys && !req.body.deleteS3Keys) { @@ -145,6 +149,11 @@ export default async function routes(fastify: FastifyInstance) { const bucket = `${req.params.bucketId}` let before = req.body.before ? new Date(req.body.before as string) : undefined + if (before && isNaN(before.getTime())) { + return reply.status(400).send({ + error: 'Invalid date format', + }) + } if (!before) { before = new Date() before.setHours(before.getHours() - 1) @@ -166,23 +175,53 @@ export default async function routes(fastify: FastifyInstance) { for await (const deleted of result) { respPing.update() - reply.raw.write( - JSON.stringify({ - ...deleted, - event: 'data', - }) + '\n' - ) + writeNdjson(reply, { + ...deleted, + event: 'data', + }) } } catch (e) { - throw e + req.log.error({ err: e, bucket }, 'delete orphaned objects stream failed') + writeNdjson(reply, { + event: 'error', + error: render(e), + }) + return } finally { respPing.clear() - reply.raw.end() + endNdjson(reply) } } ) } +function canWriteNdjson(reply: FastifyReply) { + return !reply.raw.destroyed && !reply.raw.writableEnded +} + +function writeNdjson(reply: FastifyReply, payload: unknown) { + if (!canWriteNdjson(reply)) { + return false + } + + try { + reply.raw.write(JSON.stringify(payload) + '\n') + return true + } catch { + return false + } +} + +function endNdjson(reply: FastifyReply) { + if (!canWriteNdjson(reply)) { + return + } + + try { + reply.raw.end() + } catch {} +} + // Occasionally write a ping message to the response stream function ping(reply: FastifyReply) { let lastSend = undefined as Date | undefined @@ -192,11 +231,9 @@ function ping(reply: FastifyReply) { if (!lastSend || (lastSend && lastSend < fiveSecondsEarly)) { lastSend = new Date() - reply.raw.write( - JSON.stringify({ - event: 'ping', - }) + '\n' - ) + writeNdjson(reply, { + event: 'ping', + }) } }, 1000 * 10) diff --git a/src/http/routes/admin/pprof.test.ts b/src/http/routes/admin/pprof.test.ts new file mode 100644 index 000000000..a7d1f4f3d --- /dev/null +++ b/src/http/routes/admin/pprof.test.ts @@ -0,0 +1,841 @@ +import fastify, { type FastifyInstance, FastifyReply } from 'fastify' +import { + Function as PprofFunction, + Line as PprofLine, + Location as PprofLocation, + Mapping as PprofMapping, + Sample as PprofSample, + ValueType as PprofValueType, + Profile, + StringTable, +} from 'pprof-format' +import { vi } from 'vitest' +import { signals } from '../../plugins/signals' + +const runtimeApiClient = vi.hoisted(() => ({ + getRuntimeApplications: vi.fn(), + startApplicationProfiling: vi.fn(), + stopApplicationProfiling: vi.fn(), + close: vi.fn(), +})) +const waitForMultipartPprofWindowMock = vi.hoisted(() => vi.fn()) + +vi.mock('@platformatic/control', () => ({ + RuntimeApiClient: class MockRuntimeApiClient { + getRuntimeApplications = runtimeApiClient.getRuntimeApplications + startApplicationProfiling = runtimeApiClient.startApplicationProfiling + stopApplicationProfiling = runtimeApiClient.stopApplicationProfiling + close = runtimeApiClient.close + }, +})) + +vi.mock('@platformatic/globals', () => ({ + getGlobal: vi.fn(), +})) + +vi.mock('../../plugins/apikey', () => ({ + async default() {}, +})) + +vi.mock('@internal/monitoring/pprof/multipart', async (importOriginal) => { + const actual = await importOriginal() + + return { + ...actual, + waitForMultipartPprofWindow: waitForMultipartPprofWindowMock, + } +}) + +import { getGlobal } from '@platformatic/globals' +import routes from './pprof' + +function parseMultipartParts(rawPayload: Buffer, contentType: string | string[] | undefined) { + const contentTypeValue = Array.isArray(contentType) ? contentType[0] : contentType + const boundaryMatch = contentTypeValue?.match(/boundary="?([^";]+)"?/i) + const boundary = boundaryMatch?.[1] + + if (!boundary) { + throw new Error(`Missing multipart boundary in content type: ${contentTypeValue}`) + } + + const boundaryLine = `--${boundary}` + let buffer = rawPayload + const parts: Array<{ + body: Buffer + headers: Record + }> = [] + + while (buffer.length > 0) { + const boundaryEnd = buffer.indexOf('\r\n') + if (boundaryEnd < 0) { + break + } + + const line = buffer.subarray(0, boundaryEnd).toString('latin1') + buffer = buffer.subarray(boundaryEnd + 2) + + if (line === `${boundaryLine}--`) { + break + } + + if (line !== boundaryLine) { + throw new Error(`Unexpected multipart boundary line: ${line}`) + } + + const headersEnd = buffer.indexOf('\r\n\r\n') + if (headersEnd < 0) { + throw new Error('Missing multipart headers terminator') + } + + const headers = buffer + .subarray(0, headersEnd) + .toString('latin1') + .split('\r\n') + .reduce>((acc, line) => { + const separator = line.indexOf(':') + if (separator >= 0) { + acc[line.slice(0, separator).trim().toLowerCase()] = line.slice(separator + 1).trim() + } + + return acc + }, {}) + + const contentLength = Number.parseInt(headers['content-length'] || '', 10) + if (!Number.isSafeInteger(contentLength) || contentLength < 0) { + throw new Error(`Invalid multipart content length: ${headers['content-length']}`) + } + + buffer = buffer.subarray(headersEnd + 4) + const body = buffer.subarray(0, contentLength) + buffer = buffer.subarray(contentLength) + + if (buffer.subarray(0, 2).toString('latin1') !== '\r\n') { + throw new Error('Missing multipart body terminator') + } + + buffer = buffer.subarray(2) + parts.push({ headers, body: Buffer.from(body) }) + } + + return parts +} + +function buildProfile(functionName: string, sampleValue: number) { + const stringTable = new StringTable() + const sampleType = new PprofValueType({ + type: stringTable.dedup('samples'), + unit: stringTable.dedup('count'), + }) + + const profile = new Profile({ + stringTable, + sampleType: [sampleType], + periodType: sampleType, + period: 1, + timeNanos: 1n, + durationNanos: 1_000_000_000n, + mapping: [ + new PprofMapping({ + id: 1, + hasFunctions: true, + hasFilenames: true, + hasLineNumbers: true, + }), + ], + function: [ + new PprofFunction({ + id: 1, + name: stringTable.dedup(functionName), + systemName: stringTable.dedup(functionName), + filename: stringTable.dedup(`${functionName}.ts`), + startLine: 1, + }), + ], + location: [ + new PprofLocation({ + id: 1, + mappingId: 1, + line: [new PprofLine({ functionId: 1, line: 1 })], + }), + ], + sample: [ + new PprofSample({ + locationId: [1], + value: [sampleValue], + }), + ], + defaultSampleType: stringTable.dedup('samples'), + }) + + return profile.encode() +} + +async function buildApp(options?: { onPreHandlerReply?: (reply: FastifyReply) => void }) { + const app = fastify() + app.register(signals) + if (options?.onPreHandlerReply) { + app.addHook('preHandler', async (_request, reply) => { + options.onPreHandlerReply?.(reply) + }) + } + app.register(routes) + await app.ready() + return app +} + +type RegisteredRouteHandler = (request: Record, reply: FastifyReply) => unknown + +async function buildRouteHarness() { + const hooks = new Map Promise | void>() + const handlers = new Map() + const fastify = { + addHook: vi.fn((name: string, hook: () => Promise | void) => { + hooks.set(name, hook) + }), + get: vi.fn((path: string, _options: unknown, handler: RegisteredRouteHandler) => { + handlers.set(path, handler) + }), + register: vi.fn(), + } as unknown as FastifyInstance + + await routes(fastify) + + return { + getHandler(path: string) { + const handler = handlers.get(path) + + if (!handler) { + throw new Error(`Missing registered route handler for ${path}`) + } + + return handler + }, + onClose: hooks.get('onClose') ?? (async () => {}), + } +} + +async function clearPprofRouteState() { + const harness = await buildRouteHarness() + await harness.onClose() +} + +function createDeferredPromise() { + let resolve!: () => void + let reject!: (error?: unknown) => void + const promise = new Promise((res, rej) => { + resolve = res + reject = rej + }) + + return { promise, reject, resolve } +} + +function createReplyDouble() { + const raw = { + destroyed: false, + end: vi.fn(), + socket: { + setKeepAlive: vi.fn(), + }, + writableEnded: false, + write: vi.fn(() => true), + writeHead: vi.fn(), + } + + const reply = { + hijack: vi.fn(), + raw, + send: vi.fn(), + status: vi.fn(), + } + + reply.status.mockReturnValue(reply) + reply.send.mockReturnValue(reply) + + return reply as unknown as FastifyReply +} + +function emitMultipartPings( + writer: { writeJsonPart: (payload: unknown) => boolean }, + count: number +) { + for (let index = 0; index < count; index += 1) { + writer.writeJsonPart({ + at: `2026-04-17T12:00:0${index}.000Z`, + event: 'ping', + }) + } +} + +function installMultipartWindowMock(options?: { + beforeResolve?: () => Promise | void + pingCount?: number +}) { + waitForMultipartPprofWindowMock.mockImplementation( + async ( + reply: FastifyReply, + writer: { writeJsonPart: (payload: unknown) => boolean }, + seconds: number + ) => { + if (typeof reply.raw.socket?.setKeepAlive === 'function') { + reply.raw.socket.setKeepAlive(true, 5000) + } + + emitMultipartPings(writer, options?.pingCount ?? Math.floor(seconds / 5)) + await options?.beforeResolve?.() + } + ) +} + +describe('admin pprof routes', () => { + beforeEach(async () => { + await clearPprofRouteState() + vi.clearAllMocks() + + vi.mocked(getGlobal).mockReturnValue({ + applicationId: 'storage', + workerId: 2, + } as never) + + runtimeApiClient.getRuntimeApplications.mockResolvedValue({ + applications: [{ id: 'storage', workers: 2 }], + }) + runtimeApiClient.startApplicationProfiling.mockResolvedValue(undefined) + runtimeApiClient.stopApplicationProfiling.mockResolvedValue( + buildProfile('default-worker', 3).buffer + ) + runtimeApiClient.close.mockResolvedValue(undefined) + installMultipartWindowMock() + }) + + it('captures a cpu profile for the requested worker via Watt control', async () => { + const app = await buildApp() + + try { + const response = await app.inject({ + method: 'GET', + url: '/profile?seconds=1&workerId=7&sourceMaps=true', + }) + + expect(response.statusCode).toBe(200) + expect(response.headers['content-type']).toContain('multipart/mixed; boundary=') + expect(response.headers['x-platformatic-application-id']).toBe('storage') + expect(response.headers['x-platformatic-worker-id']).toBe('7') + expect(response.headers['content-disposition']).toBeUndefined() + + const parts = parseMultipartParts(response.rawPayload, response.headers['content-type']) + expect(parts).toHaveLength(2) + expect(JSON.parse(parts[0].body.toString('utf8'))).toMatchObject({ + applicationId: 'storage', + event: 'started', + filename: 'storage-worker-7-cpu.pprof', + seconds: 1, + servingWorkerId: 2, + type: 'cpu', + workerId: 7, + }) + + const profile = Profile.decode(parts[1].body) + expect(profile.sample.map((sample) => sample.value[0])).toEqual([3]) + + expect(runtimeApiClient.startApplicationProfiling).toHaveBeenCalledWith( + process.pid, + 'storage:7', + { + intervalMicros: 1000, + type: 'cpu', + sourceMaps: true, + } + ) + expect(runtimeApiClient.stopApplicationProfiling).toHaveBeenCalledWith( + process.pid, + 'storage:7', + { + type: 'cpu', + } + ) + expect(runtimeApiClient.close).toHaveBeenCalledTimes(1) + } finally { + await app.close() + } + }) + + it('passes selected node_modules source maps through to the Watt profiler', async () => { + const app = await buildApp() + + try { + const response = await app.inject({ + method: 'GET', + url: '/profile?seconds=1&workerId=7&nodeModulesSourceMaps=next,%40next%2Fnext-server,next', + }) + + expect(response.statusCode).toBe(200) + expect(runtimeApiClient.startApplicationProfiling).toHaveBeenCalledWith( + process.pid, + 'storage:7', + { + intervalMicros: 1000, + type: 'cpu', + sourceMaps: true, + nodeModulesSourceMaps: ['next', '@next/next-server'], + } + ) + } finally { + await app.close() + } + }) + + it('accepts repeated nodeModulesSourceMaps query params', async () => { + const app = await buildApp() + + try { + const response = await app.inject({ + method: 'GET', + url: '/profile?seconds=1&workerId=7&nodeModulesSourceMaps=next&nodeModulesSourceMaps=%40next%2Fnext-server&nodeModulesSourceMaps=next', + }) + + expect(response.statusCode).toBe(200) + expect(runtimeApiClient.startApplicationProfiling).toHaveBeenCalledWith( + process.pid, + 'storage:7', + { + intervalMicros: 1000, + type: 'cpu', + sourceMaps: true, + nodeModulesSourceMaps: ['next', '@next/next-server'], + } + ) + } finally { + await app.close() + } + }) + + it('enables socket keepalive for long cpu captures', async () => { + let setKeepAliveSpy: ReturnType | undefined + + const app = await buildApp({ + onPreHandlerReply: (reply) => { + setKeepAliveSpy = vi.fn() + if (reply.raw.socket) { + reply.raw.socket.setKeepAlive = setKeepAliveSpy as typeof reply.raw.socket.setKeepAlive + } + }, + }) + + try { + const response = await app.inject({ + method: 'GET', + url: '/profile?seconds=12&workerId=7&sourceMaps=true&nodeModulesSourceMaps=next', + }) + + expect(response.statusCode).toBe(200) + expect(response.headers['content-type']).toContain('multipart/mixed; boundary=') + expect(runtimeApiClient.startApplicationProfiling).toHaveBeenCalledWith( + process.pid, + 'storage:7', + { + intervalMicros: 1000, + type: 'cpu', + sourceMaps: true, + nodeModulesSourceMaps: ['next'], + } + ) + expect(setKeepAliveSpy).toBeDefined() + expect(setKeepAliveSpy).toHaveBeenCalledTimes(1) + expect(setKeepAliveSpy).toHaveBeenCalledWith(true, 5000) + } finally { + await app.close() + } + }) + + it('streams multipart pprof parts by default', async () => { + const app = await buildApp() + + try { + const response = await app.inject({ + method: 'GET', + url: '/profile?seconds=12&workerId=7&sourceMaps=true', + }) + + expect(response.statusCode).toBe(200) + expect(response.headers['content-type']).toContain('multipart/mixed; boundary=') + expect(response.headers['x-platformatic-worker-id']).toBe('7') + expect(response.headers['content-disposition']).toBeUndefined() + + const parts = parseMultipartParts(response.rawPayload, response.headers['content-type']) + expect(parts).toHaveLength(4) + + const startedPart = JSON.parse(parts[0].body.toString('utf8')) + expect(startedPart).toMatchObject({ + applicationId: 'storage', + event: 'started', + filename: 'storage-worker-7-cpu.pprof', + seconds: 12, + servingWorkerId: 2, + type: 'cpu', + workerId: 7, + }) + + expect(JSON.parse(parts[1].body.toString('utf8'))).toMatchObject({ event: 'ping' }) + expect(JSON.parse(parts[2].body.toString('utf8'))).toMatchObject({ event: 'ping' }) + expect(parts[3].headers['content-type']).toBe('application/octet-stream') + expect(parts[3].headers['content-disposition']).toContain('storage-worker-7-cpu.pprof') + + const profile = Profile.decode(parts[3].body) + expect(profile.sample.map((sample) => sample.value[0])).toEqual([3]) + } finally { + await app.close() + } + }) + + it('rejects overlapping one-shot requests for the same worker and type', async () => { + const firstWindow = createDeferredPromise() + let firstCall = true + installMultipartWindowMock({ + beforeResolve: async () => { + if (!firstCall) { + return + } + + firstCall = false + await firstWindow.promise + }, + pingCount: 2, + }) + + const app = await buildApp() + + try { + const firstWindowStarted = createDeferredPromise() + waitForMultipartPprofWindowMock.mockImplementationOnce( + async (reply: FastifyReply, writer: { writeJsonPart: (payload: unknown) => boolean }) => { + if (typeof reply.raw.socket?.setKeepAlive === 'function') { + reply.raw.socket.setKeepAlive(true, 5000) + } + + emitMultipartPings(writer, 2) + firstWindowStarted.resolve() + await firstWindow.promise + } + ) + + const firstResponsePromise = app.inject({ + method: 'GET', + url: '/profile?seconds=12&workerId=7&sourceMaps=true', + }) + + await firstWindowStarted.promise + + const secondResponse = await app.inject({ + method: 'GET', + url: '/profile?seconds=1&workerId=7&sourceMaps=true', + }) + + expect(secondResponse.statusCode).toBe(409) + expect(secondResponse.json()).toEqual({ + message: 'Profiling is already started for service "storage:7".', + }) + + firstWindow.resolve() + const firstResponse = await firstResponsePromise + expect(firstResponse.statusCode).toBe(200) + expect(runtimeApiClient.startApplicationProfiling).toHaveBeenCalledTimes(1) + } finally { + firstWindow.resolve() + await app.close() + } + }) + + it('captures and merges heap profiles across all app workers when workerId is omitted', async () => { + runtimeApiClient.stopApplicationProfiling + .mockResolvedValueOnce(buildProfile('worker-zero', 11).buffer) + .mockResolvedValueOnce(buildProfile('worker-one', 22).buffer) + + const app = await buildApp() + + try { + const response = await app.inject({ + method: 'GET', + url: '/heap?seconds=1', + }) + + expect(response.statusCode).toBe(200) + expect(response.headers['content-type']).toContain('multipart/mixed; boundary=') + expect(response.headers['x-platformatic-worker-count']).toBe('2') + expect(response.headers['x-platformatic-worker-id']).toBeUndefined() + + expect(runtimeApiClient.getRuntimeApplications).toHaveBeenCalledWith(process.pid) + expect(runtimeApiClient.startApplicationProfiling).toHaveBeenNthCalledWith( + 1, + process.pid, + 'storage:0', + { + type: 'heap', + } + ) + expect(runtimeApiClient.startApplicationProfiling).toHaveBeenNthCalledWith( + 2, + process.pid, + 'storage:1', + { + type: 'heap', + } + ) + expect(runtimeApiClient.stopApplicationProfiling).toHaveBeenNthCalledWith( + 1, + process.pid, + 'storage:0', + { + type: 'heap', + } + ) + expect(runtimeApiClient.stopApplicationProfiling).toHaveBeenNthCalledWith( + 2, + process.pid, + 'storage:1', + { + type: 'heap', + } + ) + + const parts = parseMultipartParts(response.rawPayload, response.headers['content-type']) + expect(parts).toHaveLength(2) + expect(JSON.parse(parts[0].body.toString('utf8'))).toMatchObject({ + applicationId: 'storage', + event: 'started', + filename: 'storage-heap.pprof', + seconds: 1, + servingWorkerId: 2, + type: 'heap', + workerCount: 2, + }) + + const mergedProfile = Profile.decode(parts[1].body) + expect( + mergedProfile.sample + .map((sample) => sample.value[0]) + .sort((left, right) => Number(left) - Number(right)) + ).toEqual([11, 22]) + } finally { + await app.close() + } + }) + + it('rolls back already-started workers when a whole-app start partially fails', async () => { + runtimeApiClient.startApplicationProfiling + .mockResolvedValueOnce(undefined) + .mockRejectedValueOnce( + Object.assign(new Error('start failed'), { + code: 'PLT_CTR_FAILED_TO_START_PROFILING', + }) + ) + + const app = await buildApp() + + try { + const response = await app.inject({ + method: 'GET', + url: '/heap?seconds=1', + }) + + expect(response.statusCode).toBe(502) + expect(response.json()).toEqual({ + message: 'start failed', + }) + expect(runtimeApiClient.stopApplicationProfiling).toHaveBeenCalledTimes(1) + expect(runtimeApiClient.stopApplicationProfiling).toHaveBeenCalledWith( + process.pid, + 'storage:0', + { + type: 'heap', + } + ) + } finally { + await app.close() + } + }) + + it('emits multipart error parts when stop fails after the response has started', async () => { + runtimeApiClient.stopApplicationProfiling.mockRejectedValue( + Object.assign(new Error('stop failed'), { + code: 'PLT_CTR_FAILED_TO_STOP_PROFILING', + }) + ) + + const app = await buildApp() + + try { + const response = await app.inject({ + method: 'GET', + url: '/heap?seconds=1', + }) + + expect(response.statusCode).toBe(200) + expect(response.headers['content-type']).toContain('multipart/mixed; boundary=') + + const parts = parseMultipartParts(response.rawPayload, response.headers['content-type']) + expect(parts).toHaveLength(2) + expect(JSON.parse(parts[0].body.toString('utf8'))).toMatchObject({ + applicationId: 'storage', + event: 'started', + servingWorkerId: 2, + type: 'heap', + workerCount: 2, + }) + expect(JSON.parse(parts[1].body.toString('utf8'))).toEqual({ + event: 'error', + error: { + code: 'PLT_CTR_FAILED_TO_STOP_PROFILING', + message: 'stop failed', + statusCode: 502, + }, + }) + } finally { + await app.close() + } + }) + + it('closes the multipart writer on an aborted in-flight capture', async () => { + const harness = await buildRouteHarness() + const reply = createReplyDouble() + const controller = new AbortController() + const profileHandler = harness.getHandler('/profile') + + waitForMultipartPprofWindowMock.mockImplementationOnce( + async (_reply, _writer, _seconds, signal) => { + expect(signal).toBe(controller.signal) + controller.abort() + throw new DOMException('Aborted', 'AbortError') + } + ) + + await expect( + profileHandler( + { + query: { + seconds: 30, + workerId: 7, + }, + signals: { + disconnect: controller, + }, + }, + reply + ) + ).resolves.toBe(reply) + + expect(runtimeApiClient.startApplicationProfiling).toHaveBeenCalledWith( + process.pid, + 'storage:7', + { + intervalMicros: 1000, + type: 'cpu', + } + ) + expect(runtimeApiClient.stopApplicationProfiling).toHaveBeenCalledWith( + process.pid, + 'storage:7', + { + type: 'cpu', + } + ) + expect(runtimeApiClient.close).toHaveBeenCalledTimes(1) + expect(reply.status).not.toHaveBeenCalled() + expect(reply.send).not.toHaveBeenCalled() + expect(reply.raw.end).toHaveBeenCalledTimes(1) + expect(reply.raw.end).toHaveBeenCalledWith(expect.stringMatching(/^--pprof-.*--\r\n$/)) + + await harness.onClose() + }) + + it('drains active sessions through the registered onClose hook', async () => { + const harness = await buildRouteHarness() + const reply = createReplyDouble() + const keepOpen = createDeferredPromise() + const windowStarted = createDeferredPromise() + const profileHandler = harness.getHandler('/profile') + + waitForMultipartPprofWindowMock.mockImplementationOnce(async () => { + windowStarted.resolve() + await keepOpen.promise + }) + + const handlerPromise = profileHandler( + { + query: { + seconds: 30, + workerId: 7, + }, + signals: { + disconnect: new AbortController(), + }, + }, + reply + ) + + await windowStarted.promise + await harness.onClose() + + expect(runtimeApiClient.stopApplicationProfiling).toHaveBeenCalledTimes(1) + expect(runtimeApiClient.stopApplicationProfiling).toHaveBeenCalledWith( + process.pid, + 'storage:7', + { + type: 'cpu', + } + ) + + keepOpen.resolve() + await expect(handlerPromise).resolves.toBe(reply) + expect(reply.raw.end).toHaveBeenCalledTimes(1) + expect(runtimeApiClient.stopApplicationProfiling).toHaveBeenCalledTimes(1) + expect(runtimeApiClient.close).toHaveBeenCalledTimes(2) + }) + + it('returns 501 when the admin app is not running under Watt', async () => { + vi.mocked(getGlobal).mockReturnValue(undefined) + + const app = await buildApp() + + try { + const response = await app.inject({ + method: 'GET', + url: '/profile?seconds=1', + }) + + expect(response.statusCode).toBe(501) + expect(response.json()).toEqual({ + message: 'pprof capture is only available when running under Platformatic Watt.', + }) + expect(runtimeApiClient.startApplicationProfiling).not.toHaveBeenCalled() + expect(runtimeApiClient.stopApplicationProfiling).not.toHaveBeenCalled() + } finally { + await app.close() + } + }) + + it('maps control profiling conflicts to a 409 response', async () => { + runtimeApiClient.startApplicationProfiling.mockRejectedValue( + Object.assign(new Error('Profiling is already started for service "storage:2".'), { + code: 'PLT_CTR_PROFILING_ALREADY_STARTED', + }) + ) + + const app = await buildApp() + + try { + const response = await app.inject({ + method: 'GET', + url: '/heap', + }) + + expect(response.statusCode).toBe(409) + expect(response.json()).toEqual({ + message: 'Profiling is already started for service "storage:2".', + }) + expect(runtimeApiClient.close).toHaveBeenCalledTimes(1) + } finally { + await app.close() + } + }) +}) diff --git a/src/http/routes/admin/pprof.ts b/src/http/routes/admin/pprof.ts new file mode 100644 index 000000000..dcad5f49c --- /dev/null +++ b/src/http/routes/admin/pprof.ts @@ -0,0 +1,362 @@ +import { + createMultipartPprofWriter, + waitForMultipartPprofWindow, +} from '@internal/monitoring/pprof/multipart' +import { mergeStoppedProfileBuffers } from '@internal/monitoring/pprof/profile' +import { + asProfilingRuntimeApiClient, + buildPprofFilename, + buildPprofSessionKey, + buildPprofStartOptions, + createControlStyleError, + getKnownPprofError, + isAbortError, + normalizeNodeModulesSourceMaps, + PPROF_CONTROL_ERROR_CODES, + resolvePprofFilenameTarget, + resolveWattPprofSelection, +} from '@internal/monitoring/pprof/runtime' +import type { + ActivePprofSession, + MultipartPprofWriter, + PprofCaptureOptions, + PprofCaptureType, + ProfilingRuntimeApiClient, + WattPprofSelection, + WattPprofTarget, +} from '@internal/monitoring/pprof/types' +import { RuntimeApiClient } from '@platformatic/control' +import { FastifyInstance, FastifyReply, RequestGenericInterface } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import apiKey from '../../plugins/apikey' + +const CPU_PROFILE_SECONDS_DEFAULT = 10 +const HEAP_PROFILE_SECONDS_DEFAULT = 10 +const PPROF_SECONDS_MAX = 300 +const PPROF_RESPONSE_DESCRIPTION = + 'Returns multipart/mixed. The stream starts with JSON status parts ' + + '(`started`, `ping`, and, if capture fails after headers are sent, `error`) ' + + 'and ends with the binary profile part on success.' + +const cpuProfileSchema = { + description: `Capture a CPU pprof profile. ${PPROF_RESPONSE_DESCRIPTION}`, + querystring: { + type: 'object', + properties: { + seconds: { + type: 'integer', + minimum: 1, + maximum: PPROF_SECONDS_MAX, + default: CPU_PROFILE_SECONDS_DEFAULT, + }, + sourceMaps: { + type: 'boolean', + }, + nodeModulesSourceMaps: { + anyOf: [ + { + type: 'string', + minLength: 1, + }, + { + type: 'array', + minItems: 1, + items: { + type: 'string', + minLength: 1, + }, + }, + ], + }, + workerId: { + type: 'integer', + minimum: 0, + }, + }, + additionalProperties: false, + }, + tags: ['pprof'], +} as const + +const heapProfileSchema = { + description: `Capture a heap pprof profile. ${PPROF_RESPONSE_DESCRIPTION}`, + querystring: { + type: 'object', + properties: { + seconds: { + type: 'integer', + minimum: 1, + maximum: PPROF_SECONDS_MAX, + default: HEAP_PROFILE_SECONDS_DEFAULT, + }, + sourceMaps: { + type: 'boolean', + }, + nodeModulesSourceMaps: { + anyOf: [ + { + type: 'string', + minLength: 1, + }, + { + type: 'array', + minItems: 1, + items: { + type: 'string', + minLength: 1, + }, + }, + ], + }, + workerId: { + type: 'integer', + minimum: 0, + }, + }, + additionalProperties: false, + }, + tags: ['pprof'], +} as const + +interface CpuProfileRequest extends RequestGenericInterface { + Querystring: FromSchema +} + +interface HeapProfileRequest extends RequestGenericInterface { + Querystring: FromSchema +} + +const activePprofSessions = new Map() + +async function stopProfilingTargets( + client: ProfilingRuntimeApiClient, + targets: WattPprofTarget[], + type: PprofCaptureType +) { + return Promise.allSettled( + targets.map((target) => + client.stopApplicationProfiling(target.runtimePid, target.targetApplicationId, { + type, + }) + ) + ) +} + +async function stopPprofSession(client: ProfilingRuntimeApiClient, session: ActivePprofSession) { + activePprofSessions.delete(session.key) + const stopResults = await stopProfilingTargets(client, session.targets, session.type) + return mergeStoppedProfileBuffers(stopResults) +} + +async function startPprofSession( + client: ProfilingRuntimeApiClient, + selection: WattPprofSelection, + options: { + type: PprofCaptureType + nodeModulesSourceMaps?: string[] + sourceMaps?: boolean + } +) { + const key = buildPprofSessionKey(selection, options.type) + + if (activePprofSessions.has(key)) { + throw createControlStyleError( + PPROF_CONTROL_ERROR_CODES.profilingAlreadyStarted, + selection.requestedWorkerId === undefined + ? `Profiling is already started for application "${selection.applicationId}" (all workers).` + : `Profiling is already started for service "${selection.applicationId}:${selection.requestedWorkerId}".` + ) + } + + const session: ActivePprofSession = { + ...selection, + key, + type: options.type, + } + activePprofSessions.set(key, session) + + const startOptions = buildPprofStartOptions(options) + let startedTargets: WattPprofTarget[] = [] + + try { + const startResults = await Promise.allSettled( + selection.targets.map((target) => + client.startApplicationProfiling( + target.runtimePid, + target.targetApplicationId, + startOptions + ) + ) + ) + startedTargets = selection.targets.filter( + (_, index) => startResults[index]?.status === 'fulfilled' + ) + const failedStart = startResults.find((result) => result.status === 'rejected') + + if (failedStart?.status === 'rejected') { + throw failedStart.reason + } + + return session + } catch (error) { + activePprofSessions.delete(key) + + if (startedTargets.length > 0) { + await Promise.allSettled( + startedTargets.map((target) => + client.stopApplicationProfiling(target.runtimePid, target.targetApplicationId, { + type: options.type, + }) + ) + ) + } + + throw error + } +} + +async function captureAndSendPprof( + reply: FastifyReply, + options: PprofCaptureOptions, + client: ProfilingRuntimeApiClient = asProfilingRuntimeApiClient(new RuntimeApiClient()) +) { + try { + const selection = await resolveWattPprofSelection(client, options.workerId) + + if (!selection) { + return reply.status(501).send({ + message: 'pprof capture is only available when running under Platformatic Watt.', + }) + } + + let session: ActivePprofSession | undefined + let writer: MultipartPprofWriter | undefined + + try { + session = await startPprofSession(client, selection, options) + writer = createMultipartPprofWriter(reply, selection, options.type, options.seconds) + await waitForMultipartPprofWindow(reply, writer, options.seconds, options.signal) + + if (!activePprofSessions.has(session.key)) { + writer.close() + session = undefined + return reply + } + + const profile = await stopPprofSession(client, session) + session = undefined + + writer.writeBinaryPart( + { + 'Content-Disposition': `attachment; filename="${buildPprofFilename(resolvePprofFilenameTarget(selection), options.type)}"`, + 'Content-Type': 'application/octet-stream', + }, + profile + ) + writer.close() + return reply + } catch (error) { + if (options.signal.aborted && isAbortError(error)) { + writer?.close() + return reply + } + + const knownError = getKnownPprofError(error) + if (writer) { + writer.writeJsonPart({ + event: 'error', + error: { + code: knownError?.code, + message: + error instanceof Error + ? error.message + : 'Failed to capture a pprof profile from the Watt runtime.', + statusCode: knownError?.statusCode ?? 500, + }, + }) + writer.close() + return reply + } + + if (knownError) { + return reply.status(knownError.statusCode).send({ + message: knownError.message, + }) + } + + throw error + } finally { + // onClose clears the shared session registry before it stops any in-flight captures, so + // only the code path that still owns the registry entry should perform the final stop here. + if (session && activePprofSessions.has(session.key)) { + await stopPprofSession(client, session).catch(() => {}) + } + } + } finally { + await client.close().catch(() => {}) + } +} + +async function stopActivePprofSessions(client: ProfilingRuntimeApiClient) { + const pendingSessions = [...activePprofSessions.values()] + activePprofSessions.clear() + + if (pendingSessions.length === 0) { + return + } + + await Promise.allSettled( + pendingSessions.map((session) => stopPprofSession(client, session).catch(() => {})) + ) +} + +export default async function routes(fastify: FastifyInstance) { + fastify.register(apiKey) + fastify.addHook('onClose', async () => { + if (activePprofSessions.size === 0) { + return + } + + const client = asProfilingRuntimeApiClient(new RuntimeApiClient()) + + try { + await stopActivePprofSessions(client) + } finally { + await client.close().catch(() => {}) + } + }) + + fastify.get( + '/profile', + { schema: cpuProfileSchema }, + async (request, reply) => { + const options: PprofCaptureOptions = { + type: 'cpu', + seconds: request.query.seconds, + sourceMaps: request.query.sourceMaps, + nodeModulesSourceMaps: normalizeNodeModulesSourceMaps(request.query.nodeModulesSourceMaps), + workerId: request.query.workerId, + signal: request.signals.disconnect.signal, + } + + return captureAndSendPprof(reply, options) + } + ) + + fastify.get( + '/heap', + { schema: heapProfileSchema }, + async (request, reply) => { + const options: PprofCaptureOptions = { + type: 'heap', + seconds: request.query.seconds, + sourceMaps: request.query.sourceMaps, + nodeModulesSourceMaps: normalizeNodeModulesSourceMaps(request.query.nodeModulesSourceMaps), + workerId: request.query.workerId, + signal: request.signals.disconnect.signal, + } + + return captureAndSendPprof(reply, options) + } + ) +} diff --git a/src/http/routes/admin/queue.ts b/src/http/routes/admin/queue.ts index 7b1dd724b..2af65c583 100644 --- a/src/http/routes/admin/queue.ts +++ b/src/http/routes/admin/queue.ts @@ -1,8 +1,8 @@ -import { FastifyInstance, RequestGenericInterface } from 'fastify' -import apiKey from '../../plugins/apikey' -import { getConfig } from '../../../config' import { MoveJobs, UpgradePgBossV10 } from '@storage/events' +import { FastifyInstance, RequestGenericInterface } from 'fastify' import { FromSchema } from 'json-schema-to-ts' +import { getConfig } from '../../../config' +import apiKey from '../../plugins/apikey' const { pgQueueEnable } = getConfig() @@ -32,7 +32,7 @@ interface MoveJobsRequestInterface extends RequestGenericInterface { export default async function routes(fastify: FastifyInstance) { fastify.register(apiKey) - fastify.post('/migrate/pgboss-v10', async (req, reply) => { + fastify.post('/migrate/pgboss-v10', { schema: { tags: ['queue'] } }, async (req, reply) => { if (!pgQueueEnable) { return reply.status(400).send({ message: 'Queue is not enabled' }) } @@ -42,21 +42,25 @@ export default async function routes(fastify: FastifyInstance) { return reply.send({ message: 'Migration scheduled' }) }) - fastify.post('/move', async (req, reply) => { - if (!pgQueueEnable) { - return reply.status(400).send({ message: 'Queue is not enabled' }) - } + fastify.post( + '/move', + { schema: { tags: ['queue'] } }, + async (req, reply) => { + if (!pgQueueEnable) { + return reply.status(400).send({ message: 'Queue is not enabled' }) + } - const fromQueue = req.body.fromQueue - const toQueue = req.body.toQueue - const deleteJobsFromOriginalQueue = req.body.deleteJobsFromOriginalQueue || false + const fromQueue = req.body.fromQueue + const toQueue = req.body.toQueue + const deleteJobsFromOriginalQueue = req.body.deleteJobsFromOriginalQueue || false - await MoveJobs.send({ - fromQueue, - toQueue, - deleteJobsFromOriginalQueue, - }) + await MoveJobs.send({ + fromQueue, + toQueue, + deleteJobsFromOriginalQueue, + }) - return reply.send({ message: 'Move jobs scheduled' }) - }) + return reply.send({ message: 'Move jobs scheduled' }) + } + ) } diff --git a/src/http/routes/admin/s3.ts b/src/http/routes/admin/s3.ts index 961112bf3..aabd69c17 100644 --- a/src/http/routes/admin/s3.ts +++ b/src/http/routes/admin/s3.ts @@ -1,9 +1,9 @@ -import { FastifyInstance, RequestGenericInterface } from 'fastify' -import apiKey from '../../plugins/apikey' import { s3CredentialsManager } from '@internal/database' -import { FromSchema } from 'json-schema-to-ts' -import { isUuid } from '@storage/limits' import { ERRORS } from '@internal/errors' +import { isUuid } from '@storage/limits' +import { FastifyInstance, RequestGenericInterface } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import apiKey from '../../plugins/apikey' const createCredentialsSchema = { description: 'Create S3 Credentials', @@ -87,7 +87,7 @@ export default async function routes(fastify: FastifyInstance) { fastify.post( '/:tenantId/credentials', { - schema: createCredentialsSchema, + schema: { ...createCredentialsSchema, tags: ['s3-credentials'] }, }, async (req, reply) => { const credentials = await s3CredentialsManager.createS3Credentials(req.params.tenantId, { @@ -106,7 +106,7 @@ export default async function routes(fastify: FastifyInstance) { fastify.get( '/:tenantId/credentials', - { schema: listCredentialsSchema }, + { schema: { ...listCredentialsSchema, tags: ['s3-credentials'] } }, async (req, reply) => { const credentials = await s3CredentialsManager.listS3Credentials(req.params.tenantId) return reply.send(credentials) @@ -115,7 +115,7 @@ export default async function routes(fastify: FastifyInstance) { fastify.delete( '/:tenantId/credentials', - { schema: deleteCredentialsSchema }, + { schema: { ...deleteCredentialsSchema, tags: ['s3-credentials'] } }, async (req, reply) => { if (!isUuid(req.body.id)) { throw ERRORS.InvalidParameter('id not uuid') diff --git a/src/http/routes/admin/tenants.ts b/src/http/routes/admin/tenants.ts index 2493388aa..bf2819074 100644 --- a/src/http/routes/admin/tenants.ts +++ b/src/http/routes/admin/tenants.ts @@ -1,24 +1,29 @@ -import { FastifyInstance, RequestGenericInterface } from 'fastify' -import { FromSchema } from 'json-schema-to-ts' -import apiKey from '../../plugins/apikey' import { decrypt, encrypt } from '@internal/auth' import { deleteTenantConfig, - TenantMigrationStatus, - multitenantKnex, + getTenantCapabilities, getTenantConfig, jwksManager, - getTenantCapabilities, + multitenantKnex, + TenantMigrationStatus, } from '@internal/database' -import { dbSuperUser, storage } from '../../plugins' import { - DBMigration, + isDBMigrationName, lastLocalMigrationName, progressiveMigrations, resetMigration, runMigrationsOnTenant, + updateTenantMigrationsState, } from '@internal/database/migrations' +import { StorageBackendError } from '@internal/errors' +import { PG_BOSS_SCHEMA } from '@internal/queue' +import { RunMigrationsOnTenants } from '@storage/events' +import { FastifyInstance, RequestGenericInterface } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' import { getConfig, JwksConfigKey } from '../../../config' +import { dbSuperUser, storage } from '../../plugins' +import apiKey from '../../plugins/apikey' +import { registerJsonParserAllowingEmptyBody } from '../../plugins/empty-json-body' const patchSchema = { body: { @@ -66,6 +71,14 @@ const patchSchema = { maxCatalogs: { type: 'number' }, }, }, + vectorBuckets: { + type: 'object', + properties: { + enabled: { type: 'boolean' }, + maxBuckets: { type: 'number' }, + maxIndexes: { type: 'number' }, + }, + }, }, }, }, @@ -113,14 +126,26 @@ interface tenantDBInterface { feature_iceberg_catalog_max_tables?: number | null feature_iceberg_catalog_max_catalogs?: number | null image_transformation_max_resolution?: number + feature_vector_buckets?: boolean + feature_vector_buckets_max_buckets?: number + feature_vector_buckets_max_indexes?: number + disable_events?: string[] | null } -const { dbMigrationFreezeAt } = getConfig() +const { dbMigrationFreezeAt, icebergEnabled, vectorEnabled } = getConfig() +const migrationQueueName = RunMigrationsOnTenants.getQueueName() + +async function markTenantMigrationsCompleted(tenantId: string) { + await updateTenantMigrationsState(tenantId, { + migration: dbMigrationFreezeAt, + state: TenantMigrationStatus.COMPLETED, + }) +} export default async function routes(fastify: FastifyInstance) { fastify.register(apiKey) - fastify.get('/', async () => { + fastify.get('/', { schema: { tags: ['tenant'] } }, async () => { const tenants = await multitenantKnex('tenants').select() return tenants.map( ({ @@ -141,6 +166,9 @@ export default async function routes(fastify: FastifyInstance) { feature_iceberg_catalog_max_catalogs, feature_iceberg_catalog_max_namespaces, feature_iceberg_catalog_max_tables, + feature_vector_buckets, + feature_vector_buckets_max_buckets, + feature_vector_buckets_max_indexes, image_transformation_max_resolution, migrations_version, migrations_status, @@ -172,153 +200,178 @@ export default async function routes(fastify: FastifyInstance) { enabled: feature_s3_protocol, }, icebergCatalog: { - enabled: feature_iceberg_catalog, + enabled: icebergEnabled || feature_iceberg_catalog, maxNamespaces: feature_iceberg_catalog_max_namespaces, maxTables: feature_iceberg_catalog_max_tables, maxCatalogs: feature_iceberg_catalog_max_catalogs, }, + vectorBuckets: { + enabled: vectorEnabled || feature_vector_buckets, + maxBuckets: feature_vector_buckets_max_buckets, + maxIndexes: feature_vector_buckets_max_indexes, + }, }, disableEvents: disable_events, }) ) }) - fastify.get('/:tenantId', async (request, reply) => { - const tenant = await multitenantKnex('tenants').first().where('id', request.params.tenantId) - if (!tenant) { - return reply.code(404).send() - } - const { - anon_key, - database_url, - database_pool_url, - database_pool_mode, - max_connections, - file_size_limit, - jwt_secret, - jwks, - service_key, - feature_purge_cache, - feature_s3_protocol, - feature_image_transformation, - feature_iceberg_catalog, - feature_iceberg_catalog_max_catalogs, - feature_iceberg_catalog_max_namespaces, - feature_iceberg_catalog_max_tables, - image_transformation_max_resolution, - migrations_version, - migrations_status, - tracing_mode, - disable_events, - } = tenant - - const capabilities = await getTenantCapabilities(request.params.tenantId) - - return { - anonKey: decrypt(anon_key), - databaseUrl: decrypt(database_url), - databasePoolUrl: - typeof database_pool_url === null - ? null - : database_pool_url - ? decrypt(database_pool_url) - : undefined, - databasePoolMode: database_pool_mode, - maxConnections: max_connections ? Number(max_connections) : undefined, - fileSizeLimit: Number(file_size_limit), - jwtSecret: decrypt(jwt_secret), - jwks, - serviceKey: decrypt(service_key), - capabilities, - features: { - imageTransformation: { - enabled: feature_image_transformation, - maxResolution: image_transformation_max_resolution, - }, - purgeCache: { - enabled: feature_purge_cache, - }, - s3Protocol: { - enabled: feature_s3_protocol, - }, - icebergCatalog: { - enabled: feature_iceberg_catalog, - maxNamespaces: feature_iceberg_catalog_max_namespaces, - maxTables: feature_iceberg_catalog_max_tables, - maxCatalogs: feature_iceberg_catalog_max_catalogs, + fastify.get( + '/:tenantId', + { schema: { tags: ['tenant'] } }, + async (request, reply) => { + const tenant = await multitenantKnex('tenants').first().where('id', request.params.tenantId) + if (!tenant) { + return reply.code(404).send() + } + const { + anon_key, + database_url, + database_pool_url, + database_pool_mode, + max_connections, + file_size_limit, + jwt_secret, + jwks, + service_key, + feature_purge_cache, + feature_s3_protocol, + feature_image_transformation, + feature_iceberg_catalog, + feature_iceberg_catalog_max_catalogs, + feature_iceberg_catalog_max_namespaces, + feature_iceberg_catalog_max_tables, + feature_vector_buckets, + feature_vector_buckets_max_buckets, + feature_vector_buckets_max_indexes, + image_transformation_max_resolution, + migrations_version, + migrations_status, + tracing_mode, + disable_events, + } = tenant + + const capabilities = await getTenantCapabilities(request.params.tenantId) + + return { + anonKey: decrypt(anon_key), + databaseUrl: decrypt(database_url), + databasePoolUrl: + database_pool_url === null + ? null + : database_pool_url + ? decrypt(database_pool_url) + : undefined, + databasePoolMode: database_pool_mode, + maxConnections: max_connections ? Number(max_connections) : undefined, + fileSizeLimit: Number(file_size_limit), + jwtSecret: decrypt(jwt_secret), + jwks, + serviceKey: decrypt(service_key), + capabilities, + features: { + imageTransformation: { + enabled: feature_image_transformation, + maxResolution: image_transformation_max_resolution, + }, + purgeCache: { + enabled: feature_purge_cache, + }, + s3Protocol: { + enabled: feature_s3_protocol, + }, + icebergCatalog: { + enabled: icebergEnabled || feature_iceberg_catalog, + maxNamespaces: feature_iceberg_catalog_max_namespaces, + maxTables: feature_iceberg_catalog_max_tables, + maxCatalogs: feature_iceberg_catalog_max_catalogs, + }, + vectorBuckets: { + enabled: vectorEnabled || feature_vector_buckets, + maxBuckets: feature_vector_buckets_max_buckets, + maxIndexes: feature_vector_buckets_max_indexes, + }, }, - }, - migrationVersion: migrations_version, - migrationStatus: migrations_status, - tracingMode: tracing_mode, - disableEvents: disable_events, + migrationVersion: migrations_version, + migrationStatus: migrations_status, + tracingMode: tracing_mode, + disableEvents: disable_events, + } } - }) + ) - fastify.post('/:tenantId', { schema }, async (request, reply) => { - const { tenantId } = request.params - const { - anonKey, - databaseUrl, - databasePoolMode, - fileSizeLimit, - jwtSecret, - jwks, - serviceKey, - features, - databasePoolUrl, - maxConnections, - tracingMode, - } = request.body - - await multitenantKnex.transaction(async (trx) => { - await multitenantKnex('tenants').insert({ - id: tenantId, - anon_key: encrypt(anonKey), - database_url: encrypt(databaseUrl), - database_pool_url: databasePoolUrl ? encrypt(databasePoolUrl) : undefined, - database_pool_mode: databasePoolMode, - max_connections: maxConnections ? Number(maxConnections) : undefined, - file_size_limit: fileSizeLimit, - jwt_secret: encrypt(jwtSecret), + fastify.post( + '/:tenantId', + { schema: { ...schema, tags: ['tenant'] } }, + async (request, reply) => { + const { tenantId } = request.params + const { + anonKey, + databaseUrl, + databasePoolMode, + fileSizeLimit, + jwtSecret, jwks, - service_key: encrypt(serviceKey), - feature_image_transformation: features?.imageTransformation?.enabled ?? false, - feature_purge_cache: features?.purgeCache?.enabled ?? false, - feature_s3_protocol: features?.s3Protocol?.enabled ?? true, - feature_iceberg_catalog: features?.icebergCatalog?.enabled ?? false, - feature_iceberg_catalog_max_catalogs: features?.icebergCatalog?.maxCatalogs, - feature_iceberg_catalog_max_namespaces: features?.icebergCatalog?.maxNamespaces, - feature_iceberg_catalog_max_tables: features?.icebergCatalog?.maxTables, - migrations_version: null, - migrations_status: null, - tracing_mode: tracingMode, - }) - await jwksManager.generateUrlSigningJwk(tenantId, trx) - }) + serviceKey, + features, + databasePoolUrl, + maxConnections, + tracingMode, + disableEvents, + } = request.body - try { - await runMigrationsOnTenant({ - databaseUrl, - tenantId, - upToMigration: dbMigrationFreezeAt, + await multitenantKnex.transaction(async (trx) => { + await trx('tenants').insert({ + id: tenantId, + anon_key: encrypt(anonKey), + database_url: encrypt(databaseUrl), + database_pool_url: databasePoolUrl ? encrypt(databasePoolUrl) : undefined, + database_pool_mode: databasePoolMode, + max_connections: maxConnections ? Number(maxConnections) : undefined, + file_size_limit: fileSizeLimit, + jwt_secret: encrypt(jwtSecret), + jwks, + service_key: encrypt(serviceKey), + feature_image_transformation: features?.imageTransformation?.enabled ?? false, + feature_purge_cache: features?.purgeCache?.enabled ?? false, + feature_s3_protocol: features?.s3Protocol?.enabled ?? true, + feature_iceberg_catalog: features?.icebergCatalog?.enabled ?? false, + feature_iceberg_catalog_max_catalogs: features?.icebergCatalog?.maxCatalogs, + feature_iceberg_catalog_max_namespaces: features?.icebergCatalog?.maxNamespaces, + feature_iceberg_catalog_max_tables: features?.icebergCatalog?.maxTables, + feature_vector_buckets: features?.vectorBuckets?.enabled ?? false, + feature_vector_buckets_max_buckets: features?.vectorBuckets?.maxBuckets, + feature_vector_buckets_max_indexes: features?.vectorBuckets?.maxIndexes, + image_transformation_max_resolution: + features?.imageTransformation?.maxResolution === null + ? null + : features?.imageTransformation?.maxResolution, + migrations_version: null, + migrations_status: null, + tracing_mode: tracingMode, + disable_events: disableEvents, + }) + await jwksManager.generateUrlSigningJwk(tenantId, trx) }) - await multitenantKnex('tenants') - .where('id', tenantId) - .update({ - migrations_version: await lastLocalMigrationName(), - migrations_status: TenantMigrationStatus.COMPLETED, + + try { + await runMigrationsOnTenant({ + databaseUrl, + tenantId, + upToMigration: dbMigrationFreezeAt, }) - } catch { - progressiveMigrations.addTenant(tenantId) - } + await markTenantMigrationsCompleted(tenantId) + } catch { + progressiveMigrations.addTenant(tenantId) + } - reply.code(201).send() - }) + reply.code(201).send() + } + ) fastify.patch( '/:tenantId', - { schema: patchSchema }, + { schema: { ...patchSchema, tags: ['tenant'] } }, async (request, reply) => { const { anonKey, @@ -343,8 +396,8 @@ export default async function routes(fastify: FastifyInstance) { database_pool_url: databasePoolUrl ? encrypt(databasePoolUrl) : databasePoolUrl === null - ? null - : undefined, + ? null + : undefined, database_pool_mode: databasePoolMode, max_connections: maxConnections ? Number(maxConnections) : undefined, file_size_limit: fileSizeLimit, @@ -358,6 +411,9 @@ export default async function routes(fastify: FastifyInstance) { feature_iceberg_catalog_max_catalogs: features?.icebergCatalog?.maxCatalogs, feature_iceberg_catalog_max_namespaces: features?.icebergCatalog?.maxNamespaces, feature_iceberg_catalog_max_tables: features?.icebergCatalog?.maxTables, + feature_vector_buckets: features?.vectorBuckets?.enabled, + feature_vector_buckets_max_buckets: features?.vectorBuckets?.maxBuckets, + feature_vector_buckets_max_indexes: features?.vectorBuckets?.maxIndexes, image_transformation_max_resolution: features?.imageTransformation?.maxResolution === null ? null @@ -374,12 +430,7 @@ export default async function routes(fastify: FastifyInstance) { tenantId, upToMigration: dbMigrationFreezeAt, }) - await multitenantKnex('tenants') - .where('id', tenantId) - .update({ - migrations_version: await lastLocalMigrationName(), - migrations_status: TenantMigrationStatus.COMPLETED, - }) + await markTenantMigrationsCompleted(tenantId) } catch (e) { if (e instanceof Error) { request.executionError = e @@ -392,223 +443,266 @@ export default async function routes(fastify: FastifyInstance) { } ) - fastify.put('/:tenantId', { schema }, async (request, reply) => { - const { - anonKey, - databaseUrl, - fileSizeLimit, - jwtSecret, - jwks, - serviceKey, - features, - databasePoolUrl, - databasePoolMode, - maxConnections, - tracingMode, - } = request.body - const { tenantId } = request.params - - const tenantInfo: tenantDBInterface & { - tracing_mode?: string - } = { - id: tenantId, - anon_key: encrypt(anonKey), - database_url: encrypt(databaseUrl), - jwt_secret: encrypt(jwtSecret), - jwks: jwks || null, - service_key: encrypt(serviceKey), - } + fastify.put( + '/:tenantId', + { schema: { ...schema, tags: ['tenant'] } }, + async (request, reply) => { + const { + anonKey, + databaseUrl, + fileSizeLimit, + jwtSecret, + jwks, + serviceKey, + features, + databasePoolUrl, + databasePoolMode, + maxConnections, + tracingMode, + disableEvents, + } = request.body + const { tenantId } = request.params - if (fileSizeLimit) { - tenantInfo.file_size_limit = fileSizeLimit - } + const tenantInfo: tenantDBInterface & { + tracing_mode?: string + } = { + id: tenantId, + anon_key: encrypt(anonKey), + database_url: encrypt(databaseUrl), + jwt_secret: encrypt(jwtSecret), + jwks: jwks || null, + service_key: encrypt(serviceKey), + } - if (typeof features?.imageTransformation?.enabled !== 'undefined') { - tenantInfo.feature_image_transformation = features?.imageTransformation?.enabled - } + if (fileSizeLimit) { + tenantInfo.file_size_limit = fileSizeLimit + } - if (typeof features?.purgeCache?.enabled !== 'undefined') { - tenantInfo.feature_purge_cache = features?.purgeCache?.enabled - } + if (typeof features?.imageTransformation?.enabled !== 'undefined') { + tenantInfo.feature_image_transformation = features?.imageTransformation?.enabled + } - if (typeof features?.imageTransformation?.maxResolution !== 'undefined') { - tenantInfo.image_transformation_max_resolution = features?.imageTransformation - ?.image_transformation_max_resolution as number | undefined - } + if (typeof features?.purgeCache?.enabled !== 'undefined') { + tenantInfo.feature_purge_cache = features?.purgeCache?.enabled + } - if (typeof features?.s3Protocol?.enabled !== 'undefined') { - tenantInfo.feature_s3_protocol = features?.s3Protocol?.enabled - } + if (typeof features?.imageTransformation?.maxResolution !== 'undefined') { + tenantInfo.image_transformation_max_resolution = features?.imageTransformation + ?.maxResolution as number | undefined + } - if (databasePoolUrl) { - tenantInfo.database_pool_url = encrypt(databasePoolUrl) - } + if (typeof features?.s3Protocol?.enabled !== 'undefined') { + tenantInfo.feature_s3_protocol = features?.s3Protocol?.enabled + } - if (maxConnections) { - tenantInfo.max_connections = Number(maxConnections) - } + if (databasePoolUrl) { + tenantInfo.database_pool_url = encrypt(databasePoolUrl) + } - if (databasePoolMode) { - tenantInfo.database_pool_mode = databasePoolMode - } + if (maxConnections) { + tenantInfo.max_connections = Number(maxConnections) + } - if (tracingMode) { - tenantInfo.tracing_mode = tracingMode - } + if (databasePoolMode) { + tenantInfo.database_pool_mode = databasePoolMode + } + + if (tracingMode) { + tenantInfo.tracing_mode = tracingMode + } - if (features?.icebergCatalog?.enabled) { tenantInfo.feature_iceberg_catalog = features?.icebergCatalog?.enabled tenantInfo.feature_iceberg_catalog_max_namespaces = features?.icebergCatalog?.maxNamespaces tenantInfo.feature_iceberg_catalog_max_tables = features?.icebergCatalog?.maxTables tenantInfo.feature_iceberg_catalog_max_catalogs = features?.icebergCatalog?.maxCatalogs - } - await multitenantKnex.transaction(async (trx) => { - await trx('tenants').insert(tenantInfo).onConflict('id').merge() - await jwksManager.generateUrlSigningJwk(tenantId, trx) - }) + tenantInfo.feature_vector_buckets = features?.vectorBuckets?.enabled + tenantInfo.feature_vector_buckets_max_buckets = features?.vectorBuckets?.maxBuckets + tenantInfo.feature_vector_buckets_max_indexes = features?.vectorBuckets?.maxIndexes - try { - await runMigrationsOnTenant({ - databaseUrl, - tenantId, - upToMigration: dbMigrationFreezeAt, + if (disableEvents !== undefined) { + tenantInfo.disable_events = disableEvents + } + + await multitenantKnex.transaction(async (trx) => { + await trx('tenants').insert(tenantInfo).onConflict('id').merge() + await jwksManager.generateUrlSigningJwk(tenantId, trx) }) - await multitenantKnex('tenants') - .where('id', tenantId) - .update({ - migrations_version: await lastLocalMigrationName(), - migrations_status: TenantMigrationStatus.COMPLETED, + + try { + await runMigrationsOnTenant({ + databaseUrl, + tenantId, + upToMigration: dbMigrationFreezeAt, }) - } catch (e) { - request.executionError = e as Error - progressiveMigrations.addTenant(tenantId) + await markTenantMigrationsCompleted(tenantId) + } catch (e) { + request.executionError = e as Error + progressiveMigrations.addTenant(tenantId) + } + + reply.code(204).send() } + ) - reply.code(204).send() - }) + fastify.register(async (f) => { + registerJsonParserAllowingEmptyBody(f) - fastify.delete('/:tenantId', async (request, reply) => { - await multitenantKnex('tenants').del().where('id', request.params.tenantId) - deleteTenantConfig(request.params.tenantId) - reply.code(204).send() + f.delete( + '/:tenantId', + { schema: { tags: ['tenant'] } }, + async (request, reply) => { + await multitenantKnex('tenants').del().where('id', request.params.tenantId) + deleteTenantConfig(request.params.tenantId) + reply.code(204).send() + } + ) }) - fastify.get('/:tenantId/migrations', async (req, reply) => { - const migrationsInfo = await multitenantKnex - .table<{ migrations_version?: string; migrations_status?: string }>('tenants') - .select('migrations_version', 'migrations_status') - .where('id', req.params.tenantId) - .first() - - if (!migrationsInfo) { - reply.status(404).send({ - error: 'Tenant not found', - }) - return - } + fastify.get( + '/:tenantId/migrations', + { schema: { tags: ['tenant'] } }, + async (req, reply) => { + const migrationsInfo = await multitenantKnex + .table<{ migrations_version?: string; migrations_status?: string }>('tenants') + .select('migrations_version', 'migrations_status') + .where('id', req.params.tenantId) + .first() + + if (!migrationsInfo) { + reply.status(404).send({ + error: 'Tenant not found', + }) + return + } - reply.send({ - isLatest: (await lastLocalMigrationName()) === migrationsInfo?.migrations_version, - migrationsVersion: migrationsInfo?.migrations_version, - migrationsStatus: migrationsInfo?.migrations_status, - }) - }) + const latestMigration = dbMigrationFreezeAt || (await lastLocalMigrationName()) - fastify.post('/:tenantId/migrations', async (req, reply) => { - const tenantId = req.params.tenantId - const migrationsInfo = await multitenantKnex - .table<{ databaseUrl: string }>('tenants') - .select('database_url') - .where('id', req.params.tenantId) - .first() - - if (!migrationsInfo) { - reply.status(404).send({ - error: 'Tenant not found', + reply.send({ + isLatest: latestMigration === migrationsInfo?.migrations_version, + migrationsVersion: migrationsInfo?.migrations_version, + migrationsStatus: migrationsInfo?.migrations_status, }) - return } + ) - const databaseUrl = decrypt(migrationsInfo.database_url) + fastify.post( + '/:tenantId/migrations', + { schema: { tags: ['tenant'] } }, + async (req, reply) => { + const tenantId = req.params.tenantId + const migrationsInfo = await multitenantKnex + .table<{ databaseUrl: string }>('tenants') + .select('database_url') + .where('id', req.params.tenantId) + .first() + + if (!migrationsInfo) { + reply.status(404).send({ + error: 'Tenant not found', + }) + return + } - try { - await runMigrationsOnTenant({ - databaseUrl, - tenantId, - upToMigration: dbMigrationFreezeAt, - }) - reply.send({ - migrated: true, - }) - } catch (e) { - req.executionError = e as Error - reply.status(400).send({ - migrated: false, - error: JSON.stringify(e), - }) - } - }) + const databaseUrl = decrypt(migrationsInfo.database_url) - fastify.post('/:tenantId/migrations/reset', async (req, reply) => { - const { untilMigration, markCompletedTillMigration } = req.body + try { + await runMigrationsOnTenant({ + databaseUrl, + tenantId, + upToMigration: dbMigrationFreezeAt, + }) + await markTenantMigrationsCompleted(tenantId) + return reply.send({ + migrated: true, + }) + } catch (e) { + req.executionError = e as Error - const { databaseUrl } = await getTenantConfig(req.params.tenantId) + if (e instanceof StorageBackendError) { + return reply.status(e.httpStatusCode || 400).send({ + migrated: false, + metadata: e.metadata, + ...e.render(), + }) + } - if ( - typeof untilMigration !== 'string' || - !DBMigration[untilMigration as keyof typeof DBMigration] - ) { - return reply.status(400).send({ message: 'Invalid migration' }) + return reply.status(400).send({ + migrated: false, + error: JSON.stringify(e), + }) + } } + ) - if ( - typeof markCompletedTillMigration === 'string' && - !DBMigration[untilMigration as keyof typeof DBMigration] - ) { - return reply.status(400).send({ message: 'Invalid migration' }) - } + fastify.post( + '/:tenantId/migrations/reset', + { schema: { tags: ['tenant'] } }, + async (req, reply) => { + const { untilMigration, markCompletedTillMigration } = req.body as Record - try { - await resetMigration({ - tenantId: req.params.tenantId, - databaseUrl, - untilMigration: untilMigration as keyof typeof DBMigration, - markCompletedTillMigration: markCompletedTillMigration - ? (markCompletedTillMigration as keyof typeof DBMigration) - : undefined, - }) + const { databaseUrl } = await getTenantConfig(req.params.tenantId) - return reply.send({ message: 'Migrations reset' }) - } catch (e) { - req.executionError = e as Error - return reply.status(400).send({ message: 'Failed to reset migration' }) - } - }) + if (!isDBMigrationName(untilMigration)) { + return reply.status(400).send({ message: 'Invalid migration' }) + } - fastify.get('/:tenantId/migrations/jobs', async (req, reply) => { - const data = await multitenantKnex - .table('pgboss.job') - .select('*') - .whereRaw("data->'tenant'->>'ref' = ?", [req.params.tenantId]) - .where('name', 'tenants-migrations') - .orderBy('createdon', 'desc') - .limit(100) + if ( + typeof markCompletedTillMigration === 'string' && + !isDBMigrationName(markCompletedTillMigration) + ) { + return reply.status(400).send({ message: 'Invalid migration' }) + } - reply.send(data) - }) + try { + await resetMigration({ + tenantId: req.params.tenantId, + databaseUrl, + untilMigration, + markCompletedTillMigration: isDBMigrationName(markCompletedTillMigration) + ? markCompletedTillMigration + : undefined, + }) + + return reply.send({ message: 'Migrations reset' }) + } catch (e) { + req.executionError = e as Error + return reply.status(400).send({ message: 'Failed to reset migration' }) + } + } + ) - fastify.delete('/:tenantId/migrations/jobs', async (req, reply) => { - const data = await multitenantKnex - .table('pgboss.job') - .whereRaw("data->'tenant'->>'ref' = ?", [req.params.tenantId]) - .where('name', 'tenants-migrations') - .orderBy('createdon', 'desc') - .limit(100) - .delete() + fastify.get( + '/:tenantId/migrations/jobs', + { schema: { tags: ['tenant'] } }, + async (req, reply) => { + const data = await multitenantKnex + .table(`${PG_BOSS_SCHEMA}.job`) + .select('*') + .whereRaw("data->'tenant'->>'ref' = ?", [req.params.tenantId]) + .where('name', migrationQueueName) + .orderBy('created_on', 'desc') + .limit(100) + + reply.send(data) + } + ) - reply.send(data) - }) + fastify.delete( + '/:tenantId/migrations/jobs', + { schema: { tags: ['tenant'] } }, + async (req, reply) => { + const data = await multitenantKnex + .table(`${PG_BOSS_SCHEMA}.job`) + .whereRaw("data->'tenant'->>'ref' = ?", [req.params.tenantId]) + .where('name', migrationQueueName) + .orderBy('created_on', 'desc') + .limit(100) + .delete() + + reply.send(data) + } + ) fastify.register(async (fastify) => { fastify.register(dbSuperUser, { @@ -616,16 +710,20 @@ export default async function routes(fastify: FastifyInstance) { }) fastify.register(storage) - fastify.get('/:tenantId/health', async (req, res) => { - try { - await req.storage.healthcheck() - res.send({ healthy: true }) - } catch (e) { - if (e instanceof Error) { - req.executionError = e + fastify.get( + '/:tenantId/health', + { schema: { tags: ['tenant'] } }, + async (req, res) => { + try { + await req.storage.healthcheck() + return res.send({ healthy: true }) + } catch (e) { + if (e instanceof Error) { + req.executionError = e + } + return res.send({ healthy: false }) } - res.send({ healthy: false }) } - }) + ) }) } diff --git a/src/http/routes/bucket/createBucket.ts b/src/http/routes/bucket/createBucket.ts index 3c889586b..8fcdd8abc 100644 --- a/src/http/routes/bucket/createBucket.ts +++ b/src/http/routes/bucket/createBucket.ts @@ -3,7 +3,6 @@ import { FromSchema } from 'json-schema-to-ts' import { createDefaultSchema } from '../../routes-helper' import { AuthenticatedRequest } from '../../types' import { ROUTE_OPERATIONS } from '../operations' -import { BucketType } from '@aws-sdk/client-s3' const createBucketBodySchema = { type: 'object', diff --git a/src/http/routes/bucket/deleteBucket.ts b/src/http/routes/bucket/deleteBucket.ts index e39c8ab16..2d40fb865 100644 --- a/src/http/routes/bucket/deleteBucket.ts +++ b/src/http/routes/bucket/deleteBucket.ts @@ -1,5 +1,6 @@ import { FastifyInstance } from 'fastify' import { FromSchema } from 'json-schema-to-ts' +import { registerJsonParserAllowingEmptyBody } from '../../plugins/empty-json-body' import { createDefaultSchema, createResponse } from '../../routes-helper' import { AuthenticatedRequest } from '../../types' import { ROUTE_OPERATIONS } from '../operations' @@ -12,18 +13,6 @@ const deleteBucketParamsSchema = { required: ['bucketId'], } as const -const deleteBucketQuerySchema = { - type: 'object', - properties: { - type: { - type: 'string', - enum: ['ANALYTICS', 'STANDARD'], - default: 'STANDARD', - }, - }, - required: [], -} as const - const successResponseSchema = { type: 'object', properties: { @@ -32,7 +21,6 @@ const successResponseSchema = { } interface deleteBucketRequestInterface extends AuthenticatedRequest { Params: FromSchema - Querystring: FromSchema } export default async function routes(fastify: FastifyInstance) { @@ -42,19 +30,24 @@ export default async function routes(fastify: FastifyInstance) { summary, tags: ['bucket'], }) - fastify.delete( - '/:bucketId', - { - schema, - config: { - operation: { type: ROUTE_OPERATIONS.DELETE_BUCKET }, + + fastify.register(async (f) => { + registerJsonParserAllowingEmptyBody(f) + + f.delete( + '/:bucketId', + { + schema, + config: { + operation: { type: ROUTE_OPERATIONS.DELETE_BUCKET }, + }, }, - }, - async (request, response) => { - const { bucketId } = request.params - await request.storage.deleteBucket(bucketId, request.query.type) + async (request, response) => { + const { bucketId } = request.params + await request.storage.deleteBucket(bucketId) - return response.status(200).send(createResponse('Successfully deleted')) - } - ) + return response.status(200).send(createResponse('Successfully deleted')) + } + ) + }) } diff --git a/src/http/routes/bucket/emptyBucket.ts b/src/http/routes/bucket/emptyBucket.ts index a997dfb74..ca96671be 100644 --- a/src/http/routes/bucket/emptyBucket.ts +++ b/src/http/routes/bucket/emptyBucket.ts @@ -1,5 +1,6 @@ import { FastifyInstance } from 'fastify' import { FromSchema } from 'json-schema-to-ts' +import { registerJsonParserAllowingEmptyBody } from '../../plugins/empty-json-body' import { createDefaultSchema, createResponse } from '../../routes-helper' import { AuthenticatedRequest } from '../../types' import { ROUTE_OPERATIONS } from '../operations' @@ -31,22 +32,27 @@ export default async function routes(fastify: FastifyInstance) { summary, tags: ['bucket'], }) - fastify.post( - '/:bucketId/empty', - { - schema, - config: { - operation: { type: ROUTE_OPERATIONS.EMPTY_BUCKET }, + + fastify.register(async (f) => { + registerJsonParserAllowingEmptyBody(f) + + f.post( + '/:bucketId/empty', + { + schema, + config: { + operation: { type: ROUTE_OPERATIONS.EMPTY_BUCKET }, + }, }, - }, - async (request, response) => { - const { bucketId } = request.params + async (request, response) => { + const { bucketId } = request.params - await request.storage.emptyBucket(bucketId) + await request.storage.emptyBucket(bucketId) - return response - .status(200) - .send(createResponse('Empty bucket has been queued. Completion may take up to an hour.')) - } - ) + return response + .status(200) + .send(createResponse('Empty bucket has been queued. Completion may take up to an hour.')) + } + ) + }) } diff --git a/src/http/routes/bucket/getAllBuckets.ts b/src/http/routes/bucket/getAllBuckets.ts index 75c9cac89..83c2a3fb4 100644 --- a/src/http/routes/bucket/getAllBuckets.ts +++ b/src/http/routes/bucket/getAllBuckets.ts @@ -1,10 +1,10 @@ +import { isClientVersionBefore } from '@storage/limits' +import { bucketSchema } from '@storage/schemas' import { FastifyInstance } from 'fastify' import { FromSchema } from 'json-schema-to-ts' import { createDefaultSchema } from '../../routes-helper' import { AuthenticatedRequest } from '../../types' -import { bucketSchema } from '@storage/schemas' import { ROUTE_OPERATIONS } from '../operations' -import { isClientVersionBefore } from '@storage/limits' const successResponseSchema = { type: 'array', diff --git a/src/http/routes/bucket/getBucket.ts b/src/http/routes/bucket/getBucket.ts index 94ba37e46..b979f4c27 100644 --- a/src/http/routes/bucket/getBucket.ts +++ b/src/http/routes/bucket/getBucket.ts @@ -1,7 +1,7 @@ +import { bucketSchema } from '@storage/schemas' import { FastifyInstance } from 'fastify' import { FromSchema } from 'json-schema-to-ts' import { createDefaultSchema } from '../../routes-helper' -import { bucketSchema } from '@storage/schemas' import { AuthenticatedRequest } from '../../types' import { ROUTE_OPERATIONS } from '../operations' diff --git a/src/http/routes/bucket/index.ts b/src/http/routes/bucket/index.ts index 225466558..9c04ad941 100644 --- a/src/http/routes/bucket/index.ts +++ b/src/http/routes/bucket/index.ts @@ -1,11 +1,11 @@ import { FastifyInstance } from 'fastify' +import { db, jwt, storage } from '../../plugins' import createBucket from './createBucket' import deleteBucket from './deleteBucket' import emptyBucket from './emptyBucket' import getAllBuckets from './getAllBuckets' import getBucket from './getBucket' import updateBucket from './updateBucket' -import { storage, jwt, db } from '../../plugins' export default async function routes(fastify: FastifyInstance) { fastify.register(jwt) diff --git a/src/http/routes/bucket/updateBucket.ts b/src/http/routes/bucket/updateBucket.ts index 13d3db8b0..8575bfcbb 100644 --- a/src/http/routes/bucket/updateBucket.ts +++ b/src/http/routes/bucket/updateBucket.ts @@ -6,6 +6,7 @@ import { ROUTE_OPERATIONS } from '../operations' const updateBucketBodySchema = { type: 'object', + minProperties: 1, properties: { public: { type: 'boolean', examples: [false] }, file_size_limit: { @@ -20,6 +21,11 @@ const updateBucketBodySchema = { items: { type: 'string', examples: [['image/png', 'image/jpg']] }, }, }, + anyOf: [ + { required: ['public'] }, + { required: ['file_size_limit'] }, + { required: ['allowed_mime_types'] }, + ], } as const const updateBucketParamsSchema = { type: 'object', diff --git a/src/http/routes/cdn/index.ts b/src/http/routes/cdn/index.ts index fe327c3df..31988a20d 100644 --- a/src/http/routes/cdn/index.ts +++ b/src/http/routes/cdn/index.ts @@ -1,7 +1,7 @@ import { FastifyInstance } from 'fastify' +import { getConfig } from '../../../config' import { db, jwt, requireTenantFeature, storage } from '../../plugins' import purgeCache from './purgeCache' -import { getConfig } from '../../../config' const { dbServiceRole } = getConfig() diff --git a/src/http/routes/cdn/purgeCache.ts b/src/http/routes/cdn/purgeCache.ts index bba865890..74078ec4e 100644 --- a/src/http/routes/cdn/purgeCache.ts +++ b/src/http/routes/cdn/purgeCache.ts @@ -28,7 +28,7 @@ export default async function routes(fastify: FastifyInstance) { const schema = createDefaultSchema(successResponseSchema, { params: purgeObjectParamsSchema, summary, - tags: ['object'], + tags: ['cdn'], }) fastify.delete( diff --git a/src/http/routes/health/healthcheck.ts b/src/http/routes/health/healthcheck.ts index b6f34a897..a8ecd55ad 100644 --- a/src/http/routes/health/healthcheck.ts +++ b/src/http/routes/health/healthcheck.ts @@ -8,17 +8,18 @@ export default async function routes(fastify: FastifyInstance) { { schema: { summary, + tags: ['health'], }, }, async (req, res) => { try { await req.storage.healthcheck() - res.send({ healthy: true }) + return res.send({ healthy: true }) } catch (e) { if (e instanceof Error) { req.executionError = e } - res.send({ healthy: false }) + return res.send({ healthy: false }) } } ) diff --git a/src/http/routes/iceberg/bucket.ts b/src/http/routes/iceberg/bucket.ts new file mode 100644 index 000000000..b74fb6c98 --- /dev/null +++ b/src/http/routes/iceberg/bucket.ts @@ -0,0 +1,131 @@ +import { FastifyInstance } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import { createResponse } from '../../routes-helper' +import { AuthenticatedRequest } from '../../types' +import { ROUTE_OPERATIONS } from '../operations' + +const deleteBucketParamsSchema = { + type: 'object', + properties: { + bucketName: { type: 'string', examples: ['avatars'] }, + }, + required: ['bucketName'], +} as const + +const createBucketBodySchema = { + type: 'object', + properties: { + name: { type: 'string', examples: ['avatars'] }, + }, + required: ['name'], +} as const + +const listBucketsQuerySchema = { + type: 'object', + properties: { + limit: { type: 'integer', minimum: 1, examples: [10] }, + offset: { type: 'integer', minimum: 0, examples: [0] }, + sortColumn: { type: 'string', enum: ['id', 'name', 'created_at', 'updated_at'] }, + sortOrder: { type: 'string', enum: ['asc', 'desc'] }, + search: { type: 'string', examples: ['my-bucket'] }, + }, +} as const + +interface deleteBucketRequestInterface extends AuthenticatedRequest { + Params: FromSchema +} + +interface createBucketRequestInterface extends AuthenticatedRequest { + Body: FromSchema +} + +interface listBucketRequestInterface extends AuthenticatedRequest { + Querystring: FromSchema +} + +export default async function routes(fastify: FastifyInstance) { + fastify.register(async (f) => { + f.addContentTypeParser('application/json', { bodyLimit: 0 }, (_request, _payload, done) => { + done(null, null) + }) + + f.delete( + '/bucket/:bucketName', + { + schema: { + params: deleteBucketParamsSchema, + summary: 'Delete an analytics bucket', + tags: ['bucket'], + }, + config: { + operation: { type: ROUTE_OPERATIONS.DELETE_BUCKET }, + }, + }, + async (request, response) => { + const { bucketName } = request.params + await request.storage.deleteIcebergBucket(bucketName) + + return response.status(200).send(createResponse('Successfully deleted')) + } + ) + }) + + fastify.post( + '/bucket', + { + schema: { + body: createBucketBodySchema, + summary: 'Create an analytics bucket', + tags: ['bucket'], + }, + config: { + operation: { type: ROUTE_OPERATIONS.CREATE_BUCKET }, + }, + }, + async (request, response) => { + const { name } = request.body + const bucket = await request.storage.createIcebergBucket({ + name, + }) + + return response.status(200).send({ + id: bucket.name, + name: bucket.name, + created_at: bucket.created_at, + updated_at: bucket.updated_at, + }) + } + ) + + fastify.get( + '/bucket', + { + schema: { + querystring: listBucketsQuerySchema, + summary: 'List analytics buckets', + tags: ['bucket'], + }, + config: { + operation: { type: ROUTE_OPERATIONS.LIST_BUCKET }, + }, + }, + async (request, response) => { + const query = request.query + + const bucket = await request.storage.listAnalyticsBuckets('name,created_at,updated_at', { + limit: query.limit, + offset: query.offset, + sortColumn: query.sortColumn, + sortOrder: query.sortOrder, + search: query.search, + }) + + return response.status(200).send( + bucket.map((b) => ({ + ...b, + id: b.name, + })) + ) + } + ) +} diff --git a/src/http/routes/iceberg/catalog.ts b/src/http/routes/iceberg/catalog.ts index fbe70cd94..5685391ad 100644 --- a/src/http/routes/iceberg/catalog.ts +++ b/src/http/routes/iceberg/catalog.ts @@ -1,7 +1,7 @@ +import { ERRORS } from '@internal/errors' import { FastifyInstance } from 'fastify' -import { AuthenticatedRequest } from '../../types' import { FromSchema } from 'json-schema-to-ts' -import { ERRORS } from '@internal/errors' +import { AuthenticatedRequest } from '../../types' import { ROUTE_OPERATIONS } from '../operations' const getConfigSchema = { diff --git a/src/http/routes/iceberg/index.ts b/src/http/routes/iceberg/index.ts index 41a288954..f69ba8182 100644 --- a/src/http/routes/iceberg/index.ts +++ b/src/http/routes/iceberg/index.ts @@ -1,26 +1,36 @@ import { FastifyInstance } from 'fastify' +import { getConfig } from '../../../config' +import { setErrorHandler } from '../../error-handler' import { db, icebergRestCatalog, jwt, requireTenantFeature, storage } from '../../plugins' +import bucket from './bucket' import catalogue from './catalog' import namespace from './namespace' import table from './table' -import { setErrorHandler } from '../../error-handler' -import { getConfig } from '../../../config' -const { dbServiceRole } = getConfig() +const { dbServiceRole, icebergEnabled, isMultitenant } = getConfig() export default async function routes(fastify: FastifyInstance) { + // Disable iceberg routes if the feature is not enabled + if (!icebergEnabled && !isMultitenant) { + return + } + fastify.register(async function authenticated(fastify) { fastify.register(jwt, { enforceJwtRoles: [dbServiceRole], }) - fastify.register(requireTenantFeature('icebergCatalog')) + + if (!icebergEnabled && isMultitenant) { + fastify.register(requireTenantFeature('icebergCatalog')) + } fastify.register(db) fastify.register(storage) - fastify.register(icebergRestCatalog) - fastify.register(catalogue) - fastify.register(namespace) - fastify.register(table) + fastify.register(bucket) + fastify.register(icebergRestCatalog, { prefix: 'v1' }) + fastify.register(catalogue, { prefix: 'v1' }) + fastify.register(namespace, { prefix: 'v1' }) + fastify.register(table, { prefix: 'v1' }) setErrorHandler(fastify, { respectStatusCode: true, diff --git a/src/http/routes/iceberg/namespace.ts b/src/http/routes/iceberg/namespace.ts index 71b1ef549..105cc5149 100644 --- a/src/http/routes/iceberg/namespace.ts +++ b/src/http/routes/iceberg/namespace.ts @@ -1,7 +1,7 @@ +import { ERRORS } from '@internal/errors' import { FastifyInstance } from 'fastify' -import { AuthenticatedRequest } from '../../types' import { FromSchema } from 'json-schema-to-ts' -import { ERRORS } from '@internal/errors' +import { AuthenticatedRequest } from '../../types' import { ROUTE_OPERATIONS } from '../operations' const createNamespaceSchema = { @@ -10,6 +10,7 @@ const createNamespaceSchema = { type: 'object', properties: { namespace: { type: 'string', examples: ['namespace'] }, + properties: { type: 'object', additionalProperties: { type: 'string' } }, }, required: ['namespace'], }, @@ -104,6 +105,7 @@ export default async function routes(fastify: FastifyInstance) { const result = await request.icebergCatalog.createNamespace({ namespace: [request.body.namespace], warehouse: request.params.prefix, + properties: request.body.properties, }) return response.send(result) @@ -140,7 +142,7 @@ export default async function routes(fastify: FastifyInstance) { config: { operation: { type: ROUTE_OPERATIONS.ICEBERG_NAMESPACE_EXISTS }, }, - schema: { ...listNamespaceSchema, tags: ['iceberg'] }, + schema: { ...loadNamespaceSchema, tags: ['iceberg'] }, }, async (request, response) => { if (!request.icebergCatalog) { diff --git a/src/http/routes/iceberg/table.ts b/src/http/routes/iceberg/table.ts index bb77cd7b0..a752f0f74 100644 --- a/src/http/routes/iceberg/table.ts +++ b/src/http/routes/iceberg/table.ts @@ -1,8 +1,9 @@ -import { FastifyInstance } from 'fastify' -import { AuthenticatedRequest } from '../../types' -import { FromSchema } from 'json-schema-to-ts' import { ERRORS } from '@internal/errors' import { CreateTableRequest } from '@storage/protocols/iceberg/catalog/rest-catalog-client' +import { FastifyInstance } from 'fastify' +import JSONBigint from 'json-bigint' +import { FromSchema } from 'json-schema-to-ts' +import { AuthenticatedRequest } from '../../types' import { ROUTE_OPERATIONS } from '../operations' const createTableSchema = { @@ -234,18 +235,24 @@ const commitTransactionSchema = { items: { type: 'object', description: 'A requirement assertion', + required: ['type'], properties: { - name: { + type: { type: 'string', - description: 'Name of the requirement (e.g. assert-ref-snapshot-id)', - examples: ['assert-ref-snapshot-id'], + description: + 'Type of the requirement (e.g. assert-ref-snapshot-id, assert-table-uuid)', + examples: ['assert-ref-snapshot-id', 'assert-table-uuid'], }, + // allow arbitrary additional args specific to the requirement + ref: { type: 'string' }, + // 'snapshot-id': { type: 'number', format: 'int64', bigint: true }, + uuid: { type: 'string' }, args: { type: 'object', - description: 'Arguments for the requirement', additionalProperties: true, }, }, + additionalProperties: true, }, }, updates: { @@ -254,18 +261,54 @@ const commitTransactionSchema = { items: { type: 'object', description: 'A single update operation', + required: ['action'], properties: { - name: { + action: { type: 'string', - description: 'Name of the update operation (e.g. add-column)', - examples: ['add-column'], + description: 'Action to perform (e.g. add-snapshot, set-snapshot-ref)', + examples: ['add-snapshot', 'set-snapshot-ref'], + }, + snapshot: { + type: 'object', + properties: { + // 'snapshot-id': { type: 'string', format: 'int64', bigint: true }, + // 'parent-snapshot-id': { + // type: 'integer', + // format: 'int64', + // bigint: true, + // nullable: true, + // }, + 'sequence-number': { type: 'integer' }, + 'timestamp-ms': { type: 'integer' }, + 'manifest-list': { type: 'string' }, + summary: { + type: 'object', + additionalProperties: true, + properties: { + operation: { type: 'string' }, + 'added-files-size': { type: 'string' }, + 'added-data-files': { type: 'string' }, + 'added-records': { type: 'string' }, + 'total-delete-files': { type: 'string' }, + 'total-records': { type: 'string' }, + 'total-position-deletes': { type: 'string' }, + 'total-equality-deletes': { type: 'string' }, + }, + }, + 'schema-id': { type: 'integer' }, + }, + additionalProperties: true, }, + // Fields for set-snapshot-ref or similar actions + 'ref-name': { type: 'string' }, + type: { type: 'string' }, + // 'snapshot-id': { type: 'integer', format: 'int64', bigint: true }, args: { type: 'object', - description: 'Arguments for the update operation', additionalProperties: true, }, }, + additionalProperties: true, }, }, }, @@ -298,11 +341,30 @@ interface commitTableRequest extends AuthenticatedRequest { Body: FromSchema<(typeof commitTransactionSchema)['body']> } +const BigIntSerializer = JSONBigint({ + strict: true, + useNativeBigInt: true, +}) + export default async function routes(fastify: FastifyInstance) { + // Make sure big ints responses are serialized correctly as integers and not strings + fastify.setSerializerCompiler(() => { + return BigIntSerializer.stringify + }) + fastify.post( '/:prefix/namespaces/:namespace/tables', { - schema: { ...createTableSchema, tags: ['iceberg'] }, + schema: { + ...createTableSchema, + response: { + 200: { + type: 'object', + additionalProperties: true, + }, + }, + tags: ['iceberg'], + }, }, async (request, response) => { if (!request.icebergCatalog) { @@ -325,7 +387,16 @@ export default async function routes(fastify: FastifyInstance) { config: { operation: { type: ROUTE_OPERATIONS.ICEBERG_LIST_TABLES }, }, - schema: { ...listTableSchema, tags: ['iceberg'] }, + schema: { + ...listTableSchema, + response: { + 200: { + type: 'object', + additionalProperties: true, + }, + }, + tags: ['iceberg'], + }, }, async (request, response) => { if (!request.icebergCatalog) { @@ -349,7 +420,16 @@ export default async function routes(fastify: FastifyInstance) { config: { operation: { type: ROUTE_OPERATIONS.ICEBERG_LOAD_TABLE }, }, - schema: { ...loadTableSchema, tags: ['iceberg'] }, + schema: { + ...loadTableSchema, + response: { + 200: { + type: 'object', + additionalProperties: true, + }, + }, + tags: ['iceberg'], + }, exposeHeadRoute: false, }, async (request, response) => { @@ -373,7 +453,16 @@ export default async function routes(fastify: FastifyInstance) { config: { operation: { type: ROUTE_OPERATIONS.ICEBERG_TABLE_EXISTS }, }, - schema: { ...loadTableSchema, tags: ['iceberg'] }, + schema: { + ...loadTableSchema, + response: { + 200: { + type: 'object', + additionalProperties: true, + }, + }, + tags: ['iceberg'], + }, }, async (request, response) => { if (!request.icebergCatalog) { @@ -424,27 +513,64 @@ export default async function routes(fastify: FastifyInstance) { ) }) - fastify.post( - '/:prefix/namespaces/:namespace/tables/:table', - { - config: { - operation: { type: ROUTE_OPERATIONS.ICEBERG_COMMIT_TABLE }, - }, - schema: { ...commitTransactionSchema, tags: ['iceberg'] }, - }, - async (request, response) => { - if (!request.icebergCatalog) { - throw ERRORS.FeatureNotEnabled('icebergCatalog', 'iceberg_catalog') + fastify.register(async (fastify) => { + fastify.addContentTypeParser('application/json', {}, (_request, payload: unknown, done) => { + try { + if (typeof payload === 'string') return done(null, JSONBigint.parse(payload)) + if (Buffer.isBuffer(payload)) return done(null, JSONBigint.parse(payload.toString('utf8'))) + if (payload && typeof (payload as any).on === 'function') { + const chunks: Buffer[] = [] + ;(payload as NodeJS.ReadableStream).on('data', (c) => + chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(String(c))) + ) + ;(payload as NodeJS.ReadableStream).on('end', () => { + try { + done(null, JSONBigint.parse(Buffer.concat(chunks).toString('utf8'))) + } catch (err) { + done(err as Error) + } + }) + ;(payload as NodeJS.ReadableStream).on('error', (err) => done(err as Error)) + return + } + done(null, payload) + } catch (err) { + done(err as Error) } + }) - const result = await request.icebergCatalog.updateTable({ - ...request.body, - namespace: request.params.namespace, - table: request.params.table, - warehouse: request.params.prefix, - }) + fastify.post( + '/:prefix/namespaces/:namespace/tables/:table', + { + config: { + operation: { type: ROUTE_OPERATIONS.ICEBERG_COMMIT_TABLE }, + }, + schema: { + ...commitTransactionSchema, + response: { + 200: { + type: 'object', + additionalProperties: true, + }, + }, + tags: ['iceberg'], + }, + }, + async (request, response) => { + if (!request.icebergCatalog) { + throw ERRORS.FeatureNotEnabled('icebergCatalog', 'iceberg_catalog') + } - return response.send(result) - } - ) + const result = await request.icebergCatalog.updateTable({ + namespace: request.params.namespace, + table: request.params.table, + warehouse: request.params.prefix, + requirements: request.body.requirements, + updates: request.body.updates, + }) + + return response.send(result) + } + ) + }) } diff --git a/src/http/routes/index.ts b/src/http/routes/index.ts index d8527091d..9d4f29fd2 100644 --- a/src/http/routes/index.ts +++ b/src/http/routes/index.ts @@ -1,9 +1,10 @@ +export * from './admin' export { default as bucket } from './bucket' +export { default as cdn } from './cdn' +export { default as healthcheck } from './health' +export { default as iceberg } from './iceberg' export { default as object } from './object' export { default as render } from './render' -export { default as tus } from './tus' -export { default as healthcheck } from './health' export { default as s3 } from './s3' -export { default as iceberg } from './iceberg' -export { default as cdn } from './cdn' -export * from './admin' +export { default as tus } from './tus' +export { default as vector } from './vector' diff --git a/src/http/routes/object/copyObject.ts b/src/http/routes/object/copyObject.ts index 19f3b0563..4366c8ed5 100644 --- a/src/http/routes/object/copyObject.ts +++ b/src/http/routes/object/copyObject.ts @@ -1,10 +1,10 @@ +import { objectSchema } from '@storage/schemas' +import { parseUserMetadata } from '@storage/uploader' import { FastifyInstance, FastifyRequest } from 'fastify' import { FromSchema } from 'json-schema-to-ts' import { createDefaultSchema } from '../../routes-helper' import { AuthenticatedRequest } from '../../types' import { ROUTE_OPERATIONS } from '../operations' -import { parseUserMetadata } from '@storage/uploader' -import { objectSchema } from '@storage/schemas' const copyRequestBodySchema = { type: 'object', @@ -71,7 +71,7 @@ export default async function routes(fastify: FastifyInstance) { owner: request.owner, userMetadata: typeof userMetadata === 'string' ? parseUserMetadata(userMetadata) : undefined, - metadata: metadata, + metadata, copyMetadata: request.body.copyMetadata ?? true, upsert: request.headers['x-upsert'] === 'true', }) diff --git a/src/http/routes/object/createObject.ts b/src/http/routes/object/createObject.ts index 78a959981..9a7a2010d 100644 --- a/src/http/routes/object/createObject.ts +++ b/src/http/routes/object/createObject.ts @@ -1,8 +1,8 @@ +import fastifyMultipart from '@fastify/multipart' import { FastifyInstance, RequestGenericInterface } from 'fastify' import { FromSchema } from 'json-schema-to-ts' import { createDefaultSchema } from '../../routes-helper' import { ROUTE_OPERATIONS } from '../operations' -import fastifyMultipart from '@fastify/multipart' const createObjectParamsSchema = { type: 'object', @@ -32,6 +32,7 @@ interface createObjectRequestInterface extends RequestGenericInterface { 'content-type': string 'cache-control'?: string 'x-upsert'?: string + 'x-robots-tag'?: string } } @@ -79,7 +80,7 @@ export default async function routes(fastify: FastifyInstance) { .uploadFromRequest(request, { objectName, signal: request.signals.body.signal, - owner: owner, + owner, isUpsert, }) diff --git a/src/http/routes/object/deleteObjects.ts b/src/http/routes/object/deleteObjects.ts index 438fbb00b..3305ab593 100644 --- a/src/http/routes/object/deleteObjects.ts +++ b/src/http/routes/object/deleteObjects.ts @@ -1,9 +1,10 @@ +import { objectSchema } from '@storage/schemas/object' import { FastifyInstance, FastifyRequest } from 'fastify' import { FromSchema } from 'json-schema-to-ts' import { createDefaultSchema } from '../../routes-helper' import { AuthenticatedRequest } from '../../types' -import { objectSchema } from '@storage/schemas/object' import { ROUTE_OPERATIONS } from '../operations' + const deleteObjectsParamsSchema = { type: 'object', properties: { diff --git a/src/http/routes/object/getObject.ts b/src/http/routes/object/getObject.ts index 6ffb11775..480bed1b6 100644 --- a/src/http/routes/object/getObject.ts +++ b/src/http/routes/object/getObject.ts @@ -1,11 +1,10 @@ +import { ERRORS } from '@internal/errors' +import { Obj } from '@storage/schemas' import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify' import { FromSchema } from 'json-schema-to-ts' -import { IncomingMessage, Server, ServerResponse } from 'http' import { getConfig } from '../../../config' import { AuthenticatedRangeRequest } from '../../types' import { ROUTE_OPERATIONS } from '../operations' -import { ERRORS } from '@internal/errors' -import { Obj } from '@storage/schemas' const { storageS3Bucket } = getConfig() @@ -30,16 +29,9 @@ interface getObjectRequestInterface extends AuthenticatedRangeRequest { Querystring: FromSchema } -async function requestHandler( - request: FastifyRequest, - response: FastifyReply< - getObjectRequestInterface, - Server, - IncomingMessage, - ServerResponse, - unknown - > -) { +type GetObjectRequest = FastifyRequest + +async function requestHandler(request: GetObjectRequest, response: FastifyReply) { const { bucketName } = request.params const { download } = request.query const objectName = request.params['*'] @@ -71,10 +63,13 @@ async function requestHandler( if (bucket.public) { // request is authenticated but we still use the superUser as we don't need to check RLS - obj = await request.storage.asSuperUser().from(bucketName).findObject(objectName, 'id, version') + obj = await request.storage + .asSuperUser() + .from(bucketName) + .findObject(objectName, 'id, version, metadata') } else { // request is authenticated use RLS - obj = await request.storage.from(bucketName).findObject(objectName, 'id, version') + obj = await request.storage.from(bucketName).findObject(objectName, 'id, version, metadata') } return request.storage.renderer('asset').render(request, response, { @@ -82,6 +77,7 @@ async function requestHandler( key: s3Key, version: obj.version, download, + xRobotsTag: obj.metadata?.['xRobotsTag'] as string | undefined, signal: request.signals.disconnect.signal, }) } @@ -95,6 +91,7 @@ export default async function routes(fastify: FastifyInstance) { // @todo add success response schema here schema: { params: getObjectParamsSchema, + querystring: getObjectQuerySchema, headers: { $ref: 'authSchema#' }, summary, response: { '4xx': { $ref: 'errorSchema#', description: 'Error response' } }, diff --git a/src/http/routes/object/getObjectInfo.ts b/src/http/routes/object/getObjectInfo.ts index ed4577ff8..6da993467 100644 --- a/src/http/routes/object/getObjectInfo.ts +++ b/src/http/routes/object/getObjectInfo.ts @@ -1,11 +1,11 @@ +import { ERRORS } from '@internal/errors' +import { Obj } from '@storage/schemas' import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify' import { FromSchema } from 'json-schema-to-ts' -import { IncomingMessage, Server, ServerResponse } from 'http' import { getConfig } from '../../../config' +import { transformationOptionsSchema } from '../../schemas/transformations' import { AuthenticatedRangeRequest } from '../../types' -import { Obj } from '@storage/schemas' import { ROUTE_OPERATIONS } from '../operations' -import { ERRORS } from '@internal/errors' const { storageS3Bucket } = getConfig() @@ -18,19 +18,21 @@ const getObjectParamsSchema = { required: ['bucketName', '*'], } as const +const getObjectInfoQuerySchema = { + type: 'object', + properties: transformationOptionsSchema, +} as const + interface getObjectRequestInterface extends AuthenticatedRangeRequest { Params: FromSchema + Querystring: FromSchema } +type GetObjectInfoRequest = FastifyRequest + async function requestHandler( - request: FastifyRequest, - response: FastifyReply< - getObjectRequestInterface, - Server, - IncomingMessage, - ServerResponse, - unknown - >, + request: GetObjectInfoRequest, + response: FastifyReply, publicRoute = false, method: 'head' | 'info' = 'head' ) { @@ -92,6 +94,7 @@ export async function publicRoutes(fastify: FastifyInstance) { { schema: { params: getObjectParamsSchema, + querystring: getObjectInfoQuerySchema, summary: 'Get object info', description: 'returns object info', tags: ['object'], @@ -112,6 +115,7 @@ export async function publicRoutes(fastify: FastifyInstance) { exposeHeadRoute: false, schema: { params: getObjectParamsSchema, + querystring: getObjectInfoQuerySchema, summary: 'Get object info', description: 'returns object info', tags: ['object'], @@ -134,13 +138,14 @@ export async function authenticatedRoutes(fastify: FastifyInstance) { { schema: { params: getObjectParamsSchema, + querystring: getObjectInfoQuerySchema, headers: { $ref: 'authSchema#' }, summary, response: { '4xx': { $ref: 'errorSchema#', description: 'Error response' } }, tags: ['object'], }, config: { - operation: { type: 'object.head_authenticated_info' }, + operation: { type: ROUTE_OPERATIONS.HEAD_AUTH_OBJECT_INFO }, }, }, async (request, response) => { @@ -153,13 +158,14 @@ export async function authenticatedRoutes(fastify: FastifyInstance) { { schema: { params: getObjectParamsSchema, + querystring: getObjectInfoQuerySchema, headers: { $ref: 'authSchema#' }, summary, response: { '4xx': { $ref: 'errorSchema#', description: 'Error response' } }, tags: ['object'], }, config: { - operation: { type: 'object.get_authenticated_info' }, + operation: { type: ROUTE_OPERATIONS.GET_AUTH_OBJECT_INFO }, }, }, async (request, response) => { @@ -172,13 +178,14 @@ export async function authenticatedRoutes(fastify: FastifyInstance) { { schema: { params: getObjectParamsSchema, + querystring: getObjectInfoQuerySchema, summary, description: 'Object Info', tags: ['object'], response: { '4xx': { $ref: 'errorSchema#' } }, }, config: { - operation: { type: 'object.get_authenticated_info' }, + operation: { type: ROUTE_OPERATIONS.GET_AUTH_OBJECT_INFO }, allowInvalidJwt: true, }, }, @@ -192,13 +199,14 @@ export async function authenticatedRoutes(fastify: FastifyInstance) { { schema: { params: getObjectParamsSchema, + querystring: getObjectInfoQuerySchema, summary, description: 'Head object info', tags: ['object'], response: { '4xx': { $ref: 'errorSchema#' } }, }, config: { - operation: { type: 'object.head_authenticated_info' }, + operation: { type: ROUTE_OPERATIONS.HEAD_AUTH_OBJECT_INFO }, allowInvalidJwt: true, }, }, diff --git a/src/http/routes/object/getPublicObject.ts b/src/http/routes/object/getPublicObject.ts index 7b4a63f30..cc1b69f6d 100644 --- a/src/http/routes/object/getPublicObject.ts +++ b/src/http/routes/object/getPublicObject.ts @@ -38,6 +38,7 @@ export default async function routes(fastify: FastifyInstance) { exposeHeadRoute: false, schema: { params: getPublicObjectParamsSchema, + querystring: getObjectQuerySchema, summary, response: { '4xx': { $ref: 'errorSchema#', description: 'Error response' } }, tags: ['object'], @@ -51,11 +52,12 @@ export default async function routes(fastify: FastifyInstance) { const objectName = request.params['*'] const { download } = request.query + const bucketRef = request.storage.asSuperUser().from(bucketName) const [, obj] = await Promise.all([ request.storage.asSuperUser().findBucket(bucketName, 'id,public', { isPublic: true, }), - request.storage.asSuperUser().from(bucketName).findObject(objectName, 'id,version'), + bucketRef.findObject(objectName, 'id,version,metadata'), ]) // send the object from s3 @@ -70,6 +72,7 @@ export default async function routes(fastify: FastifyInstance) { key: s3Key, version: obj.version, download, + xRobotsTag: obj.metadata?.['xRobotsTag'] as string | undefined, signal: request.signals.disconnect.signal, }) } diff --git a/src/http/routes/object/getSignedObject.ts b/src/http/routes/object/getSignedObject.ts index fec7f34e2..d821741ca 100644 --- a/src/http/routes/object/getSignedObject.ts +++ b/src/http/routes/object/getSignedObject.ts @@ -3,8 +3,8 @@ import { FromSchema } from 'json-schema-to-ts' import { getConfig } from '../../../config' import { SignedToken, verifyJWT } from '../../../internal/auth' import { getJwtSecret } from '../../../internal/database' -import { ROUTE_OPERATIONS } from '../operations' import { ERRORS } from '../../../internal/errors' +import { ROUTE_OPERATIONS } from '../operations' const { storageS3Bucket } = getConfig() @@ -83,7 +83,7 @@ export default async function routes(fastify: FastifyInstance) { const obj = await request.storage .asSuperUser() .from(bucketName) - .findObject(objParts.join('/'), 'id,version') + .findObject(objParts.join('/'), 'id,version,metadata') return request.storage.renderer('asset').render(request, response, { bucket: storageS3Bucket, @@ -91,6 +91,7 @@ export default async function routes(fastify: FastifyInstance) { version: obj.version, download, expires: new Date(exp * 1000).toUTCString(), + xRobotsTag: obj.metadata?.['xRobotsTag'] as string | undefined, signal: request.signals.disconnect.signal, }) } diff --git a/src/http/routes/object/getSignedURL.ts b/src/http/routes/object/getSignedURL.ts index 7ba57a3c8..dae3caa41 100644 --- a/src/http/routes/object/getSignedURL.ts +++ b/src/http/routes/object/getSignedURL.ts @@ -1,10 +1,11 @@ +import { assertValidNumericJWTExpiration } from '@internal/auth' +import { isImageTransformationEnabled } from '@storage/limits' +import { ImageRenderer } from '@storage/renderer' import { FastifyInstance } from 'fastify' import { FromSchema } from 'json-schema-to-ts' import { createDefaultSchema } from '../../routes-helper' -import { AuthenticatedRequest } from '../../types' -import { ImageRenderer } from '@storage/renderer' import { transformationOptionsSchema } from '../../schemas/transformations' -import { isImageTransformationEnabled } from '@storage/limits' +import { AuthenticatedRequest } from '../../types' import { ROUTE_OPERATIONS } from '../operations' const getSignedURLParamsSchema = { @@ -18,7 +19,11 @@ const getSignedURLParamsSchema = { const getSignedURLBodySchema = { type: 'object', properties: { - expiresIn: { type: 'integer', minimum: 1, examples: [60000] }, + expiresIn: { + type: 'integer', + minimum: 1, + examples: [60000], + }, transform: { type: 'object', properties: transformationOptionsSchema, @@ -66,6 +71,7 @@ export default async function routes(fastify: FastifyInstance) { const { bucketName } = request.params const objectName = request.params['*'] const { expiresIn } = request.body + assertValidNumericJWTExpiration(expiresIn) const urlPath = request.url.split('?').shift() const imageTransformationEnabled = await isImageTransformationEnabled(request.tenantId) diff --git a/src/http/routes/object/getSignedURLs.ts b/src/http/routes/object/getSignedURLs.ts index d73af3207..510bb6dab 100644 --- a/src/http/routes/object/getSignedURLs.ts +++ b/src/http/routes/object/getSignedURLs.ts @@ -1,3 +1,4 @@ +import { assertValidNumericJWTExpiration } from '@internal/auth' import { FastifyInstance, FastifyRequest } from 'fastify' import { FromSchema } from 'json-schema-to-ts' import { createDefaultSchema } from '../../routes-helper' @@ -14,7 +15,11 @@ const getSignedURLsParamsSchema = { const getSignedURLsBodySchema = { type: 'object', properties: { - expiresIn: { type: 'integer', minimum: 1, examples: [60000] }, + expiresIn: { + type: 'integer', + minimum: 1, + examples: [60000], + }, paths: { type: 'array', items: { type: 'string' }, @@ -30,7 +35,7 @@ const successResponseSchema = { type: 'object', properties: { error: { - error: ['string', 'null'], + type: ['string', 'null'], examples: ['Either the object does not exist or you do not have access to it'], }, path: { @@ -77,6 +82,7 @@ export default async function routes(fastify: FastifyInstance) { async (request, response) => { const { bucketName } = request.params const { expiresIn, paths } = request.body + assertValidNumericJWTExpiration(expiresIn) const signedURLs = await request.storage.from(bucketName).signObjectUrls(paths, expiresIn) diff --git a/src/http/routes/object/getSignedUploadURL.ts b/src/http/routes/object/getSignedUploadURL.ts index 5a68afa30..0de648d0a 100644 --- a/src/http/routes/object/getSignedUploadURL.ts +++ b/src/http/routes/object/getSignedUploadURL.ts @@ -1,8 +1,9 @@ import { FastifyInstance } from 'fastify' import { FromSchema } from 'json-schema-to-ts' +import { getConfig } from '../../../config' +import { parseUserMetadata } from '../../../storage/uploader' import { createDefaultSchema } from '../../routes-helper' import { AuthenticatedRequest } from '../../types' -import { getConfig } from '../../../config' import { ROUTE_OPERATIONS } from '../operations' const { uploadSignedUrlExpirationTime } = getConfig() @@ -20,6 +21,9 @@ const getSignedUploadURLHeadersSchema = { type: 'object', properties: { 'x-upsert': { type: 'string' }, + 'x-metadata': { type: 'string' }, + 'content-type': { type: 'string' }, + 'content-length': { type: 'string' }, authorization: { type: 'string' }, }, required: ['authorization'], @@ -69,10 +73,29 @@ export default async function routes(fastify: FastifyInstance) { const urlPath = `${bucketName}/${objectName}` + let userMetadata: Record | undefined + + const customMd = request.headers['x-metadata'] + + if (typeof customMd === 'string') { + // TODO: parseUserMetadata casts to Record but values could be anything; + // validation should be added in a follow-up + userMetadata = parseUserMetadata(customMd) + } + + const contentType = request.headers['content-type'] + const contentLengthHeader = request.headers['content-length'] + const contentLength = contentLengthHeader ? Number(contentLengthHeader) : undefined + const signedUpload = await request.storage .from(bucketName) .signUploadObjectUrl(objectName, urlPath as string, uploadSignedUrlExpirationTime, owner, { upsert: request.headers['x-upsert'] === 'true', + userMetadata, + metadata: { + mimetype: contentType, + contentLength, + }, }) return response.status(200).send({ url: signedUpload.url, token: signedUpload.token }) diff --git a/src/http/routes/object/index.ts b/src/http/routes/object/index.ts index 8ab6d75ba..3c3c7461f 100644 --- a/src/http/routes/object/index.ts +++ b/src/http/routes/object/index.ts @@ -1,23 +1,23 @@ import { FastifyInstance } from 'fastify' -import { jwt, storage, dbSuperUser, db } from '../../plugins' +import { db, dbSuperUser, jwt, storage } from '../../plugins' import copyObject from './copyObject' import createObject from './createObject' import deleteObject from './deleteObject' import deleteObjects from './deleteObjects' import getObject from './getObject' +import { + authenticatedRoutes as getObjectInfoAuth, + publicRoutes as getObjectInfoPublic, +} from './getObjectInfo' import getPublicObject from './getPublicObject' import getSignedObject from './getSignedObject' +import getSignedUploadURL from './getSignedUploadURL' import getSignedURL from './getSignedURL' import getSignedURLs from './getSignedURLs' import listObjects from './listObjects' import listObjectsV2 from './listObjectsV2' import moveObject from './moveObject' import updateObject from './updateObject' -import { - publicRoutes as getObjectInfoPublic, - authenticatedRoutes as getObjectInfoAuth, -} from './getObjectInfo' -import getSignedUploadURL from './getSignedUploadURL' import uploadSignedObject from './uploadSignedObject' export default async function routes(fastify: FastifyInstance) { diff --git a/src/http/routes/object/listObjects.ts b/src/http/routes/object/listObjects.ts index e10f3b405..b9cafcc1e 100644 --- a/src/http/routes/object/listObjects.ts +++ b/src/http/routes/object/listObjects.ts @@ -1,8 +1,9 @@ +import { objectSchema } from '@storage/schemas' import { FastifyInstance } from 'fastify' +import { FastifyRequest } from 'fastify/types/request' import { FromSchema } from 'json-schema-to-ts' import { createDefaultSchema } from '../../routes-helper' import { AuthenticatedRequest } from '../../types' -import { objectSchema } from '@storage/schemas' import { ROUTE_OPERATIONS } from '../operations' const searchRequestParamsSchema = { @@ -40,7 +41,6 @@ interface searchRequestInterface extends AuthenticatedRequest { Body: FromSchema Params: FromSchema } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export default async function routes(fastify: FastifyInstance) { const summary = 'Search for objects under a prefix' @@ -57,6 +57,12 @@ export default async function routes(fastify: FastifyInstance) { schema, config: { operation: { type: ROUTE_OPERATIONS.LIST_OBJECTS }, + logMetadata: (req: FastifyRequest) => ({ + prefix: req.body.prefix, + limit: req.body.limit, + offset: req.body.offset, + sortBy: req.body.sortBy, + }), }, }, async (request, response) => { diff --git a/src/http/routes/object/listObjectsV2.ts b/src/http/routes/object/listObjectsV2.ts index a799c6686..b766d91f5 100644 --- a/src/http/routes/object/listObjectsV2.ts +++ b/src/http/routes/object/listObjectsV2.ts @@ -1,10 +1,11 @@ +import { getTenantConfig } from '@internal/database' +import { DBMigration } from '@internal/database/migrations' import { FastifyInstance } from 'fastify' +import { FastifyRequest } from 'fastify/types/request' import { FromSchema } from 'json-schema-to-ts' +import { getConfig } from '../../../config' import { AuthenticatedRequest } from '../../types' import { ROUTE_OPERATIONS } from '../operations' -import { getConfig } from '../../../config' -import { getTenantConfig } from '@internal/database' -import { DBMigration } from '@internal/database/migrations' const { isMultitenant } = getConfig() @@ -23,13 +24,20 @@ const searchRequestBodySchema = { limit: { type: 'integer', minimum: 1, examples: [10] }, cursor: { type: 'string' }, with_delimiter: { type: 'boolean' }, + sortBy: { + type: 'object', + properties: { + column: { type: 'string', enum: ['name', 'updated_at', 'created_at'] }, + order: { type: 'string', enum: ['asc', 'desc'] }, + }, + required: ['column'], + }, }, } as const interface searchRequestInterface extends AuthenticatedRequest { Body: FromSchema Params: FromSchema } -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export default async function routes(fastify: FastifyInstance) { const summary = 'Search for objects under a prefix' @@ -44,6 +52,13 @@ export default async function routes(fastify: FastifyInstance) { }, config: { operation: { type: ROUTE_OPERATIONS.LIST_OBJECTS_V2 }, + logMetadata: (req: FastifyRequest) => ({ + prefix: req.body.prefix, + limit: req.body.limit, + cursor: req.body.cursor, + sortBy: req.body.sortBy, + with_delimiter: req.body.with_delimiter, + }), }, }, async (request, response) => { @@ -57,13 +72,14 @@ export default async function routes(fastify: FastifyInstance) { } const { bucketName } = request.params - const { limit, with_delimiter, cursor, prefix } = request.body + const { limit, with_delimiter, cursor, prefix, sortBy } = request.body const results = await request.storage.from(bucketName).listObjectsV2({ prefix, delimiter: with_delimiter ? '/' : undefined, maxKeys: limit, cursor, + sortBy, }) return response.status(200).send(results) diff --git a/src/http/routes/object/updateObject.ts b/src/http/routes/object/updateObject.ts index d68d04669..ad0c812b4 100644 --- a/src/http/routes/object/updateObject.ts +++ b/src/http/routes/object/updateObject.ts @@ -1,8 +1,8 @@ +import fastifyMultipart from '@fastify/multipart' import { FastifyInstance, RequestGenericInterface } from 'fastify' import { FromSchema } from 'json-schema-to-ts' import { createDefaultSchema } from '../../routes-helper' import { ROUTE_OPERATIONS } from '../operations' -import fastifyMultipart from '@fastify/multipart' const updateObjectParamsSchema = { type: 'object', @@ -29,6 +29,7 @@ interface updateObjectRequestInterface extends RequestGenericInterface { 'content-type': string 'cache-control'?: string 'x-upsert'?: string + 'x-robots-tag'?: string } } @@ -74,7 +75,7 @@ export default async function routes(fastify: FastifyInstance) { .uploadFromRequest(request, { objectName, signal: request.signals.body.signal, - owner: owner, + owner, isUpsert: true, }) diff --git a/src/http/routes/object/uploadSignedObject.ts b/src/http/routes/object/uploadSignedObject.ts index 5b01aa467..d0c280ebe 100644 --- a/src/http/routes/object/uploadSignedObject.ts +++ b/src/http/routes/object/uploadSignedObject.ts @@ -1,7 +1,7 @@ +import fastifyMultipart from '@fastify/multipart' import { FastifyInstance } from 'fastify' import { FromSchema } from 'json-schema-to-ts' import { ROUTE_OPERATIONS } from '../operations' -import fastifyMultipart from '@fastify/multipart' const uploadSignedObjectParamsSchema = { type: 'object', diff --git a/src/http/routes/operations.ts b/src/http/routes/operations.ts index e14571cf6..c54e3bc3b 100644 --- a/src/http/routes/operations.ts +++ b/src/http/routes/operations.ts @@ -1,3 +1,6 @@ +// This file defines all the operation names used in the application for better maintainability and to avoid typos. +// This file is linked from the docs to provide a list of all available operations. +// Any change to this file should be reflected in the docs if applicable. export const ROUTE_OPERATIONS = { // Bucket CREATE_BUCKET: 'storage.bucket.create', @@ -14,8 +17,10 @@ export const ROUTE_OPERATIONS = { DELETE_OBJECTS: 'storage.object.delete_many', GET_PUBLIC_OBJECT: 'storage.object.get_public', GET_AUTH_OBJECT: 'storage.object.get_authenticated', - INFO_AUTH_OBJECT: 'storage.object.info_authenticated', + INFO_AUTH_OBJECT: 'storage.object.info_authenticated', // not used INFO_PUBLIC_OBJECT: 'storage.object.info_public', + GET_AUTH_OBJECT_INFO: 'object.get_authenticated_info', // legacy + HEAD_AUTH_OBJECT_INFO: 'object.head_authenticated_info', // legacy GET_SIGNED_OBJECT: 'storage.object.get_signed', SIGN_UPLOAD_URL: 'storage.object.sign_upload_url', SIGN_OBJECT_URL: 'storage.object.sign', @@ -25,7 +30,9 @@ export const ROUTE_OPERATIONS = { MOVE_OBJECT: 'storage.object.move', UPDATE_OBJECT: 'storage.object.upload_update', UPLOAD_SIGN_OBJECT: 'storage.object.upload_signed', - PURGE_OBJECT_CACHE: 'storage.object.purge_cache', + + // CDN + PURGE_OBJECT_CACHE: 'storage.cdn.purge_object_cache', // Image Transformation RENDER_AUTH_IMAGE: 'storage.render.image_authenticated', @@ -80,4 +87,21 @@ export const ROUTE_OPERATIONS = { ICEBERG_CREATE_TABLE: 'storage.iceberg.table.create', ICEBERG_DROP_TABLE: 'storage.iceberg.table.drop', ICEBERG_COMMIT_TABLE: 'storage.iceberg.table.commit', + + // Vector + CREATE_VECTOR_BUCKET: 'storage.vector.bucket.create', + DELETE_VECTOR_BUCKET: 'storage.vector.bucket.delete', + LIST_VECTOR_BUCKETS: 'storage.vector.bucket.list', + GET_VECTOR_BUCKET: 'storage.vector.bucket.get', + + CREATE_VECTOR_INDEX: 'storage.vector.index.create', + DELETE_VECTOR_INDEX: 'storage.vector.index.delete', + LIST_VECTOR_INDEXES: 'storage.vector.index.list', + GET_VECTOR_INDEX: 'storage.vector.index.get', + + GET_VECTORS: 'storage.vector.vectors.get', + PUT_VECTORS: 'storage.vector.vectors.put', + LIST_VECTORS: 'storage.vector.vectors.list', + QUERY_VECTORS: 'storage.vector.vectors.query', + DELETE_VECTORS: 'storage.vector.vectors.delete', } diff --git a/src/http/routes/render/index.ts b/src/http/routes/render/index.ts index 78cc00d4e..2fc3c236d 100644 --- a/src/http/routes/render/index.ts +++ b/src/http/routes/render/index.ts @@ -1,10 +1,10 @@ import { FastifyInstance } from 'fastify' -import renderPublicImage from './renderPublicImage' -import renderAuthenticatedImage from './renderAuthenticatedImage' -import renderSignedImage from './renderSignedImage' -import { jwt, storage, requireTenantFeature, db, dbSuperUser } from '../../plugins' import { getConfig } from '../../../config' +import { db, dbSuperUser, jwt, requireTenantFeature, storage } from '../../plugins' import { rateLimiter } from './rate-limiter' +import renderAuthenticatedImage from './renderAuthenticatedImage' +import renderPublicImage from './renderPublicImage' +import renderSignedImage from './renderSignedImage' const { imageTransformationEnabled, rateLimiterEnabled } = getConfig() diff --git a/src/http/routes/render/rate-limiter.ts b/src/http/routes/render/rate-limiter.ts index 50385cd86..05fedade2 100644 --- a/src/http/routes/render/rate-limiter.ts +++ b/src/http/routes/render/rate-limiter.ts @@ -1,6 +1,6 @@ +import fastifyRateLimit from '@fastify/rate-limit' import { FastifyInstance } from 'fastify' import fp from 'fastify-plugin' -import fastifyRateLimit from '@fastify/rate-limit' import Redis from 'ioredis' import { getConfig } from '../../../config' @@ -28,7 +28,7 @@ export const rateLimiter = fp((fastify: FastifyInstance, ops: any, done: () => v commandTimeout: rateLimiterRedisCommandTimeout * 1000, }) : undefined, - keyGenerator: function (request) { + keyGenerator(request) { const tenant = request.tenantId const ip = request.headers['x-real-ip'] || request.headers['x-client-ip'] || request.ip diff --git a/src/http/routes/render/renderAuthenticatedImage.ts b/src/http/routes/render/renderAuthenticatedImage.ts index e681ffcd2..e352d5199 100644 --- a/src/http/routes/render/renderAuthenticatedImage.ts +++ b/src/http/routes/render/renderAuthenticatedImage.ts @@ -1,10 +1,10 @@ -import { getConfig } from '../../../config' -import { FromSchema } from 'json-schema-to-ts' -import { FastifyInstance } from 'fastify' +import { getTenantConfig } from '@internal/database' import { ImageRenderer } from '@storage/renderer' +import { FastifyInstance } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import { getConfig } from '../../../config' import { transformationOptionsSchema } from '../../schemas/transformations' import { ROUTE_OPERATIONS } from '../operations' -import { getTenantConfig } from '@internal/database' const { storageS3Bucket, isMultitenant } = getConfig() @@ -51,7 +51,9 @@ export default async function routes(fastify: FastifyInstance) { const { bucketName } = request.params const objectName = request.params['*'] - const obj = await request.storage.from(bucketName).findObject(objectName, 'id,version') + const obj = await request.storage + .from(bucketName) + .findObject(objectName, 'id,version,metadata') const s3Key = request.storage.location.getKeyLocation({ tenantId: request.tenantId, @@ -73,6 +75,7 @@ export default async function routes(fastify: FastifyInstance) { key: s3Key, version: obj.version, download, + xRobotsTag: obj.metadata?.['xRobotsTag'] as string | undefined, signal: request.signals.disconnect.signal, }) } diff --git a/src/http/routes/render/renderPublicImage.ts b/src/http/routes/render/renderPublicImage.ts index 64f175e18..caf76b2be 100644 --- a/src/http/routes/render/renderPublicImage.ts +++ b/src/http/routes/render/renderPublicImage.ts @@ -1,10 +1,10 @@ -import { getConfig } from '../../../config' -import { FromSchema } from 'json-schema-to-ts' -import { FastifyInstance } from 'fastify' +import { getTenantConfig } from '@internal/database' import { ImageRenderer } from '@storage/renderer' +import { FastifyInstance } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import { getConfig } from '../../../config' import { transformationOptionsSchema } from '../../schemas/transformations' import { ROUTE_OPERATIONS } from '../operations' -import { getTenantConfig } from '@internal/database' const { storageS3Bucket, isMultitenant } = getConfig() @@ -51,11 +51,12 @@ export default async function routes(fastify: FastifyInstance) { const { bucketName } = request.params const objectName = request.params['*'] + const bucketRef = request.storage.asSuperUser().from(bucketName) const [, obj] = await Promise.all([ request.storage.asSuperUser().findBucket(bucketName, 'id,public', { isPublic: true, }), - request.storage.asSuperUser().from(bucketName).findObject(objectName, 'id,version'), + bucketRef.findObject(objectName, 'id,version,metadata'), ]) const s3Key = `${request.tenantId}/${bucketName}/${objectName}` @@ -74,6 +75,7 @@ export default async function routes(fastify: FastifyInstance) { key: s3Key, version: obj.version, download, + xRobotsTag: obj.metadata?.['xRobotsTag'] as string | undefined, signal: request.signals.disconnect.signal, }) } diff --git a/src/http/routes/render/renderSignedImage.ts b/src/http/routes/render/renderSignedImage.ts index b451b7907..3bf51dc0c 100644 --- a/src/http/routes/render/renderSignedImage.ts +++ b/src/http/routes/render/renderSignedImage.ts @@ -1,11 +1,9 @@ -import { FromSchema } from 'json-schema-to-ts' -import { FastifyInstance } from 'fastify' - import { SignedToken, verifyJWT } from '@internal/auth' import { getJwtSecret, getTenantConfig } from '@internal/database' import { ERRORS } from '@internal/errors' - import { ImageRenderer } from '@storage/renderer' +import { FastifyInstance } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' import { getConfig } from '../../../config' import { ROUTE_OPERATIONS } from '../operations' @@ -83,7 +81,7 @@ export default async function routes(fastify: FastifyInstance) { const obj = await request.storage .asSuperUser() .from(bucketName) - .findObject(objParts.join('/'), 'id,version') + .findObject(objParts.join('/'), 'id,version,metadata') const renderer = request.storage.renderer('image') as ImageRenderer @@ -102,6 +100,7 @@ export default async function routes(fastify: FastifyInstance) { version: obj.version, download, expires: new Date(exp * 1000).toUTCString(), + xRobotsTag: obj.metadata?.['xRobotsTag'] as string | undefined, signal: request.signals.disconnect.signal, }) } diff --git a/src/http/routes/s3/commands/abort-multipart-upload.ts b/src/http/routes/s3/commands/abort-multipart-upload.ts index c8a4afc33..cfed7b60f 100644 --- a/src/http/routes/s3/commands/abort-multipart-upload.ts +++ b/src/http/routes/s3/commands/abort-multipart-upload.ts @@ -1,7 +1,7 @@ +import { ERRORS } from '@internal/errors' import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' import { ROUTE_OPERATIONS } from '../../operations' -import { ERRORS } from '@internal/errors' +import { S3Router } from '../router' const AbortMultiPartUploadInput = { summary: 'Abort MultiPart Upload', diff --git a/src/http/routes/s3/commands/complete-multipart-upload.ts b/src/http/routes/s3/commands/complete-multipart-upload.ts index ee16dd43d..84c453133 100644 --- a/src/http/routes/s3/commands/complete-multipart-upload.ts +++ b/src/http/routes/s3/commands/complete-multipart-upload.ts @@ -1,7 +1,7 @@ +import { ERRORS } from '@internal/errors' import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' import { ROUTE_OPERATIONS } from '../../operations' -import { ERRORS } from '@internal/errors' +import { S3Router } from '../router' const CompletedMultipartUpload = { summary: 'Complete multipart upload', @@ -82,7 +82,7 @@ export default function CompleteMultipartUpload(s3Router: S3Router) { Bucket: req.Params.Bucket, Key: req.Params['*'], ChecksumCRC32: resp.ChecksumCRC32, - ChecksumCRC32C: resp.ChecksumCRC32, + ChecksumCRC32C: resp.ChecksumCRC32C, ChecksumSHA1: resp.ChecksumSHA1, ChecksumSHA256: resp.ChecksumSHA256, ETag: resp.ETag, diff --git a/src/http/routes/s3/commands/copy-object.ts b/src/http/routes/s3/commands/copy-object.ts index 29d5992a6..d626cb760 100644 --- a/src/http/routes/s3/commands/copy-object.ts +++ b/src/http/routes/s3/commands/copy-object.ts @@ -1,7 +1,7 @@ +import { MetadataDirective } from '@aws-sdk/client-s3' import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' import { ROUTE_OPERATIONS } from '../../operations' -import { MetadataDirective } from '@aws-sdk/client-s3' +import { S3Router } from '../router' const CopyObjectInput = { summary: 'Copy Object', diff --git a/src/http/routes/s3/commands/create-bucket.ts b/src/http/routes/s3/commands/create-bucket.ts index 0d825b782..a7cdeb6d3 100644 --- a/src/http/routes/s3/commands/create-bucket.ts +++ b/src/http/routes/s3/commands/create-bucket.ts @@ -1,6 +1,6 @@ import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' import { ROUTE_OPERATIONS } from '../../operations' +import { S3Router } from '../router' const CreateBucketInput = { summary: 'Create Bucket', diff --git a/src/http/routes/s3/commands/create-multipart-upload.ts b/src/http/routes/s3/commands/create-multipart-upload.ts index 6914400be..9c325beaa 100644 --- a/src/http/routes/s3/commands/create-multipart-upload.ts +++ b/src/http/routes/s3/commands/create-multipart-upload.ts @@ -1,8 +1,7 @@ +import { ERRORS } from '@internal/errors' import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' import { ROUTE_OPERATIONS } from '../../operations' -import { S3Backend } from '@storage/backend' -import { ERRORS } from '@internal/errors' +import { S3Router } from '../router' const CreateMultiPartUploadInput = { summary: 'Create multipart upload', @@ -41,12 +40,10 @@ export default function CreateMultipartUpload(s3Router: S3Router) { { type: 'iceberg', schema: CreateMultiPartUploadInput, + allowEmptyJsonBody: true, operation: ROUTE_OPERATIONS.S3_CREATE_MULTIPART, }, async (req, ctx) => { - const s3Protocol = new S3ProtocolHandler(ctx.storage, ctx.tenantId, ctx.owner) - - const metadata = s3Protocol.parseMetadataHeaders(req.Headers) const icebergBucketName = ctx.req.internalIcebergBucketName if (!icebergBucketName) { @@ -58,8 +55,7 @@ export default function CreateMultipartUpload(s3Router: S3Router) { req.Params['*'], undefined, req.Headers?.['content-type'] || 'application/octet-stream', - req.Headers?.['cache-control'] || 'no-cache', - metadata + req.Headers?.['cache-control'] || 'no-cache' ) return { @@ -76,7 +72,11 @@ export default function CreateMultipartUpload(s3Router: S3Router) { s3Router.post( '/:Bucket/*?uploads', - { schema: CreateMultiPartUploadInput, operation: ROUTE_OPERATIONS.S3_CREATE_MULTIPART }, + { + schema: CreateMultiPartUploadInput, + allowEmptyJsonBody: true, + operation: ROUTE_OPERATIONS.S3_CREATE_MULTIPART, + }, (req, ctx) => { const s3Protocol = new S3ProtocolHandler(ctx.storage, ctx.tenantId, ctx.owner) @@ -89,7 +89,7 @@ export default function CreateMultipartUpload(s3Router: S3Router) { CacheControl: req.Headers?.['cache-control'], ContentDisposition: req.Headers?.['content-disposition'], ContentEncoding: req.Headers?.['content-encoding'], - Metadata: metadata, + Metadata: metadata || {}, }) } ) diff --git a/src/http/routes/s3/commands/delete-bucket.ts b/src/http/routes/s3/commands/delete-bucket.ts index 17315fe34..a5039af50 100644 --- a/src/http/routes/s3/commands/delete-bucket.ts +++ b/src/http/routes/s3/commands/delete-bucket.ts @@ -1,6 +1,6 @@ import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' import { ROUTE_OPERATIONS } from '../../operations' +import { S3Router } from '../router' const DeleteBucketInput = { summary: 'Delete Bucket', diff --git a/src/http/routes/s3/commands/delete-object.ts b/src/http/routes/s3/commands/delete-object.ts index 7ef70830e..f04117ef4 100644 --- a/src/http/routes/s3/commands/delete-object.ts +++ b/src/http/routes/s3/commands/delete-object.ts @@ -1,7 +1,7 @@ import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' -import { ROUTE_OPERATIONS } from '../../operations' import { getConfig } from '../../../../config' +import { ROUTE_OPERATIONS } from '../../operations' +import { S3Router } from '../router' const DeleteObjectInput = { summary: 'Delete Object', diff --git a/src/http/routes/s3/commands/get-bucket.ts b/src/http/routes/s3/commands/get-bucket.ts index 7703b6b2e..30f249aef 100644 --- a/src/http/routes/s3/commands/get-bucket.ts +++ b/src/http/routes/s3/commands/get-bucket.ts @@ -1,6 +1,6 @@ import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' import { ROUTE_OPERATIONS } from '../../operations' +import { S3Router } from '../router' const GetBucketLocationInput = { Params: { diff --git a/src/http/routes/s3/commands/get-object.ts b/src/http/routes/s3/commands/get-object.ts index dfd920bba..8b777d466 100644 --- a/src/http/routes/s3/commands/get-object.ts +++ b/src/http/routes/s3/commands/get-object.ts @@ -1,6 +1,7 @@ +import { ERRORS } from '@internal/errors' import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' import { ROUTE_OPERATIONS } from '../../operations' +import { S3Router } from '../router' const GetObjectInput = { summary: 'Get Object', @@ -20,7 +21,17 @@ const GetObjectInput = { 'if-modified-since': { type: 'string' }, }, }, - Querystring: {}, + Querystring: { + type: 'object', + properties: { + 'response-content-disposition': { type: 'string' }, + 'response-content-type': { type: 'string' }, + 'response-cache-control': { type: 'string' }, + 'response-content-encoding': { type: 'string' }, + 'response-content-language': { type: 'string' }, + 'response-expires': { type: 'string' }, + }, + }, } as const const GetObjectTagging = { @@ -42,6 +53,16 @@ const GetObjectTagging = { }, } as const +function parseDateHeader(input?: string) { + if (input) { + const parsedDate = new Date(input) + if (isNaN(parsedDate.getTime())) { + throw ERRORS.InvalidParameter('response-expires') + } + return parsedDate + } +} + export default function GetObject(s3Router: S3Router) { s3Router.get( '/:Bucket/*?tagging', @@ -63,6 +84,7 @@ export default function GetObject(s3Router: S3Router) { const s3Protocol = new S3ProtocolHandler(ctx.storage, ctx.tenantId, ctx.owner) const ifModifiedSince = req.Headers?.['if-modified-since'] const icebergBucket = ctx.req.internalIcebergBucketName + const responseExpires = parseDateHeader(req.Querystring?.['response-expires']) return s3Protocol.getObject( { @@ -71,6 +93,12 @@ export default function GetObject(s3Router: S3Router) { Range: req.Headers?.['range'], IfNoneMatch: req.Headers?.['if-none-match'], IfModifiedSince: ifModifiedSince ? new Date(ifModifiedSince) : undefined, + ResponseContentDisposition: req.Querystring?.['response-content-disposition'], + ResponseContentType: req.Querystring?.['response-content-type'], + ResponseCacheControl: req.Querystring?.['response-cache-control'], + ResponseContentEncoding: req.Querystring?.['response-content-encoding'], + ResponseContentLanguage: req.Querystring?.['response-content-language'], + ResponseExpires: responseExpires, }, { skipDbCheck: true, @@ -86,6 +114,7 @@ export default function GetObject(s3Router: S3Router) { (req, ctx) => { const s3Protocol = new S3ProtocolHandler(ctx.storage, ctx.tenantId, ctx.owner) const ifModifiedSince = req.Headers?.['if-modified-since'] + const responseExpires = parseDateHeader(req.Querystring?.['response-expires']) return s3Protocol.getObject( { @@ -94,6 +123,12 @@ export default function GetObject(s3Router: S3Router) { Range: req.Headers?.['range'], IfNoneMatch: req.Headers?.['if-none-match'], IfModifiedSince: ifModifiedSince ? new Date(ifModifiedSince) : undefined, + ResponseContentDisposition: req.Querystring?.['response-content-disposition'], + ResponseContentType: req.Querystring?.['response-content-type'], + ResponseCacheControl: req.Querystring?.['response-cache-control'], + ResponseContentEncoding: req.Querystring?.['response-content-encoding'], + ResponseContentLanguage: req.Querystring?.['response-content-language'], + ResponseExpires: responseExpires, }, { signal: ctx.signals.response, diff --git a/src/http/routes/s3/commands/head-bucket.ts b/src/http/routes/s3/commands/head-bucket.ts index acdb20e6a..1d0419c00 100644 --- a/src/http/routes/s3/commands/head-bucket.ts +++ b/src/http/routes/s3/commands/head-bucket.ts @@ -1,8 +1,10 @@ import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' import { ROUTE_OPERATIONS } from '../../operations' +import { S3Router } from '../router' const HeadBucketInput = { + summary: 'Head Bucket', + tags: ['s3'], Params: { type: 'object', properties: { diff --git a/src/http/routes/s3/commands/head-object.ts b/src/http/routes/s3/commands/head-object.ts index 870fd4961..5de596f4d 100644 --- a/src/http/routes/s3/commands/head-object.ts +++ b/src/http/routes/s3/commands/head-object.ts @@ -1,10 +1,11 @@ +import { ERRORS } from '@internal/errors' import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' import { ROUTE_OPERATIONS } from '../../operations' -import { ERRORS } from '@internal/errors' +import { S3Router } from '../router' const HeadObjectInput = { summary: 'Head Object', + tags: ['s3'], Params: { type: 'object', properties: { diff --git a/src/http/routes/s3/commands/list-buckets.ts b/src/http/routes/s3/commands/list-buckets.ts index dd520bd80..09535e66c 100644 --- a/src/http/routes/s3/commands/list-buckets.ts +++ b/src/http/routes/s3/commands/list-buckets.ts @@ -1,6 +1,6 @@ import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' import { ROUTE_OPERATIONS } from '../../operations' +import { S3Router } from '../router' const ListObjectsInput = { summary: 'List buckets', diff --git a/src/http/routes/s3/commands/list-multipart-uploads.ts b/src/http/routes/s3/commands/list-multipart-uploads.ts index 5d8c2d1ee..14ccd3540 100644 --- a/src/http/routes/s3/commands/list-multipart-uploads.ts +++ b/src/http/routes/s3/commands/list-multipart-uploads.ts @@ -1,6 +1,6 @@ import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' import { ROUTE_OPERATIONS } from '../../operations' +import { S3Router } from '../router' const ListObjectsInput = { summary: 'List Objects', diff --git a/src/http/routes/s3/commands/list-objects.ts b/src/http/routes/s3/commands/list-objects.ts index b4c97e3c1..36b1d1e66 100644 --- a/src/http/routes/s3/commands/list-objects.ts +++ b/src/http/routes/s3/commands/list-objects.ts @@ -1,6 +1,6 @@ import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' import { ROUTE_OPERATIONS } from '../../operations' +import { S3Router } from '../router' const ListObjectsV2Input = { summary: 'List Objects V2', diff --git a/src/http/routes/s3/commands/list-parts.ts b/src/http/routes/s3/commands/list-parts.ts index 62779a0d7..0171c968a 100644 --- a/src/http/routes/s3/commands/list-parts.ts +++ b/src/http/routes/s3/commands/list-parts.ts @@ -1,8 +1,8 @@ +import { ERRORS } from '@internal/errors' +import { S3Backend } from '@storage/backend' import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' import { ROUTE_OPERATIONS } from '../../operations' -import { S3Backend } from '@storage/backend' -import { ERRORS } from '@internal/errors' +import { S3Router } from '../router' const ListPartsInput = { summary: 'List Parts', diff --git a/src/http/routes/s3/commands/put-object.ts b/src/http/routes/s3/commands/put-object.ts index 7c0712a4a..03a8ae708 100644 --- a/src/http/routes/s3/commands/put-object.ts +++ b/src/http/routes/s3/commands/put-object.ts @@ -1,12 +1,12 @@ -import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' -import { ROUTE_OPERATIONS } from '../../operations' import { MultipartFields } from '@fastify/multipart' -import { fileUploadFromRequest, getStandardMaxFileSizeLimit } from '@storage/uploader' import { ERRORS } from '@internal/errors' -import { pipeline } from 'stream/promises' import { ByteLimitTransformStream } from '@storage/protocols/s3/byte-limit-stream' -import stream, { PassThrough, Readable } from 'stream' +import { MAX_PART_SIZE, S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' +import { fileUploadFromRequest, getStandardMaxFileSizeLimit } from '@storage/uploader' +import stream, { Readable, Transform } from 'stream' +import { pipeline } from 'stream/promises' +import { ROUTE_OPERATIONS } from '../../operations' +import { S3Router } from '../router' const PutObjectInput = { summary: 'Put Object', @@ -49,6 +49,36 @@ const PostFormInput = { }, } as const +type PipelineBody = NodeJS.ReadableStream +type PipelineHandlerInput = AsyncIterable + +function withReadableStreamHandler(handler: (fileStream: Readable) => Promise) { + return async (fileStream: PipelineHandlerInput) => { + // stream/promises exposes the final stream to the destination callback + // as a generic async iterable. In these handlers the upstream is always + // a Node readable, so narrow once here. + return handler(fileStream as Readable) + } +} + +function pipelineWithOptionalStreamingSignature( + body: PipelineBody, + limit: number, + streamingSignatureV4: Transform | undefined, + handler: (fileStream: Readable) => Promise +) { + if (streamingSignatureV4) { + return pipeline( + body, + streamingSignatureV4, + new ByteLimitTransformStream(limit), + withReadableStreamHandler(handler) + ) + } + + return pipeline(body, new ByteLimitTransformStream(limit), withReadableStreamHandler(handler)) +} + export default function PutObject(s3Router: S3Router) { s3Router.put( '/:Bucket/*', @@ -81,19 +111,20 @@ export default function PutObject(s3Router: S3Router) { throw ERRORS.InvalidParameter('internalIcebergBucketName') } - return pipeline( + return pipelineWithOptionalStreamingSignature( uploadRequest.body, - new ByteLimitTransformStream(uploadRequest.maxFileSize), - ctx.req.streamingSignatureV4 || new PassThrough(), + MAX_PART_SIZE, + ctx.req.streamingSignatureV4, async (fileStream) => { const u = await ctx.req.storage.backend.uploadObject( icebergBucket, key, undefined, - fileStream as Readable, + fileStream, uploadRequest.mimeType, uploadRequest.cacheControl, - ctx.signals.body + ctx.signals.body, + uploadRequest.contentLength ) return { @@ -135,23 +166,28 @@ export default function PutObject(s3Router: S3Router) { fileSizeLimit: bucket.file_size_limit || undefined, }) - return pipeline( + return pipelineWithOptionalStreamingSignature( uploadRequest.body, - new ByteLimitTransformStream(uploadRequest.maxFileSize), - ctx.req.streamingSignatureV4 || new PassThrough(), + uploadRequest.maxFileSize, + ctx.req.streamingSignatureV4, async (fileStream) => { return s3Protocol.putObject( { - Body: fileStream as Readable, + Body: fileStream, Bucket: req.Params.Bucket, Key: key, CacheControl: uploadRequest.cacheControl, ContentType: uploadRequest.mimeType, + ContentLength: uploadRequest.contentLength, Expires: req.Headers?.['expires'] ? new Date(req.Headers?.['expires']) : undefined, ContentEncoding: req.Headers?.['content-encoding'], Metadata: metadata, }, - { signal: ctx.signals.body, isTruncated: uploadRequest.isTruncated } + { + signal: ctx.signals.body, + isTruncated: uploadRequest.isTruncated, + declaredContentLength: uploadRequest.declaredContentLength, + } ) } ) @@ -204,23 +240,26 @@ export default function PutObject(s3Router: S3Router) { } function fieldsToObject(fields: MultipartFields) { - return Object.keys(fields).reduce((acc, key) => { - const field = fields[key] - if (Array.isArray(field)) { - return acc - } + return Object.keys(fields).reduce( + (acc, key) => { + const field = fields[key] + if (Array.isArray(field)) { + return acc + } - if (!field) { - return acc - } + if (!field) { + return acc + } - if ( - field.type === 'field' && - (typeof field.value === 'string' || field.value === 'number' || field.value === 'boolean') - ) { - acc[field.fieldname.toLowerCase()] = field.value - } + if ( + field.type === 'field' && + (typeof field.value === 'string' || field.value === 'number' || field.value === 'boolean') + ) { + acc[field.fieldname.toLowerCase()] = field.value + } - return acc - }, {} as Record) + return acc + }, + {} as Record + ) } diff --git a/src/http/routes/s3/commands/upload-part-copy.ts b/src/http/routes/s3/commands/upload-part-copy.ts index 41db976c5..7f9cc8ce5 100644 --- a/src/http/routes/s3/commands/upload-part-copy.ts +++ b/src/http/routes/s3/commands/upload-part-copy.ts @@ -1,6 +1,6 @@ import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' import { ROUTE_OPERATIONS } from '../../operations' +import { S3Router } from '../router' const UploadPartCopyInput = { summary: 'Upload Part Copy', diff --git a/src/http/routes/s3/commands/upload-part.ts b/src/http/routes/s3/commands/upload-part.ts index efab75244..d1fd34804 100644 --- a/src/http/routes/s3/commands/upload-part.ts +++ b/src/http/routes/s3/commands/upload-part.ts @@ -1,8 +1,9 @@ -import { S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' -import { S3Router } from '../router' -import { ROUTE_OPERATIONS } from '../../operations' -import { pipeline } from 'stream/promises' +import { ByteLimitTransformStream } from '@storage/protocols/s3/byte-limit-stream' +import { MAX_PART_SIZE, S3ProtocolHandler } from '@storage/protocols/s3/s3-handler' import { PassThrough, Readable } from 'stream' +import { pipeline } from 'stream/promises' +import { ROUTE_OPERATIONS } from '../../operations' +import { S3Router } from '../router' const UploadPartInput = { summary: 'Upload Part', @@ -49,32 +50,47 @@ export default function UploadPart(s3Router: S3Router) { if (ctx.req.streamingSignatureV4) { const passThrough = new PassThrough() + passThrough.on('error', () => {}) + ctx.req.raw.pipe(passThrough) ctx.req.raw.on('error', (err) => { passThrough.destroy(err) }) - return pipeline(passThrough, ctx.req.streamingSignatureV4, async (body) => { - const part = await ctx.req.storage.backend.uploadPart( - icebergBucketName!, - req.Params['*'], - '', - req.Querystring.uploadId, - req.Querystring.partNumber, - body as Readable, - req.Headers?.['x-amz-decoded-content-length'] || req.Headers?.['content-length'], - ctx.signals.body - ) + return pipeline( + passThrough, + ctx.req.streamingSignatureV4, + new ByteLimitTransformStream(MAX_PART_SIZE), // 5GB max part size + async (body) => { + const part = await ctx.req.storage.backend.uploadPart( + icebergBucketName!, + req.Params['*'], + '', + req.Querystring.uploadId, + req.Querystring.partNumber, + body as Readable, + req.Headers?.['x-amz-decoded-content-length'] || req.Headers?.['content-length'], + ctx.signals.body + ) - return { - headers: { - etag: part.ETag || '', - 'Access-Control-Expose-Headers': 'etag', - }, + return { + headers: { + etag: part.ETag || '', + 'Access-Control-Expose-Headers': 'etag', + }, + } } - }) + ) } + const passThrough = new PassThrough() + passThrough.on('error', () => {}) + + ctx.req.raw.pipe(passThrough) + ctx.req.raw.on('error', (err) => { + passThrough.destroy(err) + }) + const part = await ctx.req.storage.backend.uploadPart( icebergBucketName!, req.Params['*'], @@ -107,24 +123,30 @@ export default function UploadPart(s3Router: S3Router) { if (ctx.req.streamingSignatureV4) { const passThrough = new PassThrough() + passThrough.on('error', () => {}) ctx.req.raw.pipe(passThrough) ctx.req.raw.on('error', (err) => { passThrough.destroy(err) }) - return pipeline(passThrough, ctx.req.streamingSignatureV4, async (body) => { - return s3Protocol.uploadPart( - { - Body: body as Readable, - UploadId: req.Querystring?.uploadId, - Bucket: req.Params.Bucket, - Key: req.Params['*'], - ContentLength: req.Headers?.['x-amz-decoded-content-length'], - PartNumber: req.Querystring?.partNumber, - }, - { signal: ctx.req.signals.body.signal } - ) - }) + return pipeline( + passThrough, + ctx.req.streamingSignatureV4, + new ByteLimitTransformStream(MAX_PART_SIZE), + async (body) => { + return s3Protocol.uploadPart( + { + Body: body as Readable, + UploadId: req.Querystring?.uploadId, + Bucket: req.Params.Bucket, + Key: req.Params['*'], + ContentLength: req.Headers?.['x-amz-decoded-content-length'], + PartNumber: req.Querystring?.partNumber, + }, + { signal: ctx.req.signals.body.signal } + ) + } + ) } return s3Protocol.uploadPart( diff --git a/src/http/routes/s3/error-handler.ts b/src/http/routes/s3/error-handler.ts index a6e7fe1ff..df9e40f45 100644 --- a/src/http/routes/s3/error-handler.ts +++ b/src/http/routes/s3/error-handler.ts @@ -1,9 +1,9 @@ +import { S3ServiceException } from '@aws-sdk/client-s3' import { FastifyError } from '@fastify/error' -import { FastifyRequest } from 'fastify/types/request' +import { ErrorCode, StorageBackendError } from '@internal/errors' import { FastifyReply } from 'fastify/types/reply' -import { S3ServiceException } from '@aws-sdk/client-s3' +import { FastifyRequest } from 'fastify/types/request' import { DatabaseError } from 'pg' -import { ErrorCode, StorageBackendError } from '@internal/errors' export const s3ErrorHandler = ( error: FastifyError | Error, @@ -69,6 +69,19 @@ export const s3ErrorHandler = ( }) } + const statusCode = + 'statusCode' in error && typeof error.statusCode === 'number' ? error.statusCode : undefined + + if (statusCode && statusCode >= 400 && statusCode < 500) { + return reply.status(statusCode).send({ + Error: { + Resource: resource, + Code: ErrorCode.InvalidRequest, + Message: error.message, + }, + }) + } + return reply.status(500).send({ Error: { Resource: resource, diff --git a/src/http/routes/s3/index.ts b/src/http/routes/s3/index.ts index 3eef01c8b..0594c0edd 100644 --- a/src/http/routes/s3/index.ts +++ b/src/http/routes/s3/index.ts @@ -1,19 +1,18 @@ -import { FastifyInstance, RouteHandlerMethod } from 'fastify' import fastifyMultipart from '@fastify/multipart' +import { FastifyInstance, RouteHandlerMethod } from 'fastify' import { JSONSchema } from 'json-schema-to-ts' -import { trace } from '@opentelemetry/api' +import { getConfig } from '../../../config' import { db, - xmlParser, + detectS3IcebergBucket, + icebergRestCatalog, requireTenantFeature, signatureV4, storage, - detectS3IcebergBucket, - icebergRestCatalog, + xmlParser, } from '../../plugins' -import { findArrayPathsInSchemas, getRouter, RequestInput } from './router' import { s3ErrorHandler } from './error-handler' -import { getConfig } from '../../../config' +import { findArrayPathsInSchemas, getRouter, RequestInput, RouteQuery } from './router' const { s3ProtocolEnabled } = getConfig() @@ -32,8 +31,6 @@ export default async function routes(fastify: FastifyInstance) { return } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore const methods = new Set(routes.map((e) => e.method)) methods.forEach((method) => { @@ -44,7 +41,7 @@ export default async function routes(fastify: FastifyInstance) { if ( s3Router.matchRoute(route, { type: req.isIcebergBucket ? 'iceberg' : undefined, - query: (req.query as Record) || {}, + query: (req.query as RouteQuery) || {}, headers: (req.headers as Record) || {}, }) ) { @@ -65,8 +62,8 @@ export default async function routes(fastify: FastifyInstance) { req.operation = { type: operation } - if (req.operation.type) { - trace.getActiveSpan()?.setAttribute('http.operation', req.operation.type) + if (req.operation.type && typeof req.opentelemetry === 'function') { + req.opentelemetry()?.span?.setAttribute('http.operation', req.operation.type) } const data: RequestInput = { @@ -79,11 +76,17 @@ export default async function routes(fastify: FastifyInstance) { const isValid = compiler(data) if (!isValid) { - throw { validation: compiler.errors } + const validationError = new Error('Invalid request') as Error & { + validation?: unknown + statusCode?: number + } + validationError.validation = compiler.errors + validationError.statusCode = 400 + throw validationError } const output = await route.handler(data, { - req: req, + req, storage: req.storage, tenantId: req.tenantId, owner: req.owner, @@ -129,6 +132,7 @@ export default async function routes(fastify: FastifyInstance) { const disableContentParser = routesByMethod?.some( (route) => route.disableContentTypeParser ) + const allowEmptyJsonBody = routesByMethod?.some((route) => route.allowEmptyJsonBody) if (disableContentParser) { localFastify.addContentTypeParser( @@ -137,6 +141,29 @@ export default async function routes(fastify: FastifyInstance) { done(null) } ) + } else if (allowEmptyJsonBody) { + const defaultJsonParser = localFastify.getDefaultJsonParser( + localFastify.initialConfig.onProtoPoisoning ?? 'error', + localFastify.initialConfig.onConstructorPoisoning ?? 'error' + ) + + localFastify.addContentTypeParser( + 'application/json', + { parseAs: 'string' }, + (request, body, done) => { + const requestUrl = new URL(request.url, 'http://storage.local') + const allowsEmptyBody = requestUrl.searchParams.has('uploads') + + if (!body && allowsEmptyBody) { + done(null, null) + return + } + + const jsonBody = typeof body === 'string' ? body : body.toString('utf8') + + defaultJsonParser(request, jsonBody, done) + } + ) } localFastify.register(fastifyMultipart, { @@ -149,7 +176,7 @@ export default async function routes(fastify: FastifyInstance) { localFastify.register(signatureV4) localFastify.register(xmlParser, { - disableContentParser: disableContentParser, + disableContentParser, parseAsArray: findArrayPathsInSchemas( routesByMethod.filter((r) => r.schema.Body).map((r) => r.schema.Body as JSONSchema) ), diff --git a/src/http/routes/s3/router.test.ts b/src/http/routes/s3/router.test.ts new file mode 100644 index 000000000..be0c48a52 --- /dev/null +++ b/src/http/routes/s3/router.test.ts @@ -0,0 +1,325 @@ +import { vi } from 'vitest' +import { S3ProtocolHandler } from '../../../storage/protocols/s3/s3-handler' +import { Uploader } from '../../../storage/uploader' +import CompleteMultipartUpload from './commands/complete-multipart-upload' +import { Router, type S3Router } from './router' + +afterEach(() => { + vi.restoreAllMocks() +}) + +describe('S3 router query matching', () => { + it('parses key-only query params with an undefined value', () => { + const router = new Router() + + expect(router.parseQueryMatch('uploads')).toEqual({ + key: 'uploads', + value: undefined, + }) + }) + + it('matches key-only query params when the property exists', () => { + const router = new Router() + + router.post( + '/:Bucket/*?uploads', + { + schema: {}, + operation: 'test.operation', + }, + async () => ({}) + ) + + const route = router.routes().get('/:Bucket/*')?.[0] + expect(route).toBeDefined() + + expect( + router.matchRoute(route!, { + query: { uploads: undefined }, + headers: {}, + }) + ).toBe(true) + }) + + it('matches valued query params when the value matches', () => { + const router = new Router() + + router.get( + '/:Bucket/*?list-type=2', + { + schema: {}, + operation: 'test.operation', + }, + async () => ({}) + ) + + const route = router.routes().get('/:Bucket/*')?.[0] + expect(route).toBeDefined() + + expect( + router.matchRoute(route!, { + query: { 'list-type': '2' }, + headers: {}, + }) + ).toBe(true) + }) + + it('does not match valued query params when the value differs', () => { + const router = new Router() + + router.get( + '/:Bucket/*?list-type=2', + { + schema: {}, + operation: 'test.operation', + }, + async () => ({}) + ) + + const route = router.routes().get('/:Bucket/*')?.[0] + expect(route).toBeDefined() + + expect( + router.matchRoute(route!, { + query: { 'list-type': '1' }, + headers: {}, + }) + ).toBe(false) + }) + + it('matches wildcard routes even when the request has query params', () => { + const router = new Router() + + router.get( + '/:Bucket/*', + { + schema: {}, + operation: 'test.operation', + }, + async () => ({}) + ) + + const route = router.routes().get('/:Bucket/*')?.[0] + expect(route).toBeDefined() + + expect( + router.matchRoute(route!, { + query: { uploads: undefined }, + headers: {}, + }) + ).toBe(true) + }) +}) + +describe('S3ProtocolHandler.parseMetadataHeaders', () => { + it('returns only x-amz-meta headers without the prefix', () => { + const handler = new S3ProtocolHandler({} as any, 'tenant-id') + + expect( + handler.parseMetadataHeaders({ + 'content-type': 'application/json', + 'x-amz-meta-color': 'blue', + 'x-amz-meta-size': 'large', + }) + ).toEqual({ + color: 'blue', + size: 'large', + }) + }) + + it('returns undefined when there are no metadata headers', () => { + const handler = new S3ProtocolHandler({} as any, 'tenant-id') + + expect( + handler.parseMetadataHeaders({ + authorization: 'token', + 'content-type': 'application/json', + }) + ).toBeUndefined() + }) + + it('keeps only string metadata values', () => { + const handler = new S3ProtocolHandler({} as any, 'tenant-id') + + expect( + handler.parseMetadataHeaders({ + 'x-amz-meta-color': 'blue', + 'x-amz-meta-count': 1, + 'x-amz-meta-enabled': true, + 'x-amz-meta-tags': ['a', 'b'], + 'x-amz-meta-config': { mode: 'fast' }, + }) + ).toEqual({ + color: 'blue', + }) + }) + + it('returns undefined when metadata headers are present but none are strings', () => { + const handler = new S3ProtocolHandler({} as any, 'tenant-id') + + expect( + handler.parseMetadataHeaders({ + 'x-amz-meta-count': 1, + 'x-amz-meta-enabled': false, + 'x-amz-meta-tags': ['a', 'b'], + }) + ).toBeUndefined() + }) +}) + +describe('CompleteMultipartUpload route mapping', () => { + it('maps ChecksumCRC32C from the backend response on iceberg routes', async () => { + const router = new Router() + const completeMultipartUpload = vi.fn().mockResolvedValue({ + ChecksumCRC32: 'crc32-value', + ChecksumCRC32C: 'crc32c-value', + ChecksumSHA1: 'sha1-value', + ChecksumSHA256: 'sha256-value', + ETag: 'etag-value', + }) + + CompleteMultipartUpload(router as unknown as S3Router) + + const route = router + .routes() + .get('/:Bucket/*') + ?.find((candidate) => candidate.method === 'post' && candidate.type === 'iceberg') + + expect(route).toBeDefined() + + const response = await route!.handler!( + { + Params: { + Bucket: 'public-bucket', + '*': 'folder/object.txt', + }, + Querystring: { + uploadId: 'upload-id', + }, + Body: { + CompleteMultipartUpload: { + Part: [{ PartNumber: 1, ETag: 'part-etag' }], + }, + }, + } as never, + { + req: { + internalIcebergBucketName: 'iceberg-bucket', + storage: { + backend: { + completeMultipartUpload, + }, + }, + }, + } as never + ) + + expect(completeMultipartUpload).toHaveBeenCalledWith( + 'iceberg-bucket', + 'folder/object.txt', + 'upload-id', + '', + [{ PartNumber: 1, ETag: 'part-etag' }] + ) + expect(response).toMatchObject({ + responseBody: { + CompleteMultipartUploadResult: { + ChecksumCRC32: 'crc32-value', + ChecksumCRC32C: 'crc32c-value', + ChecksumSHA1: 'sha1-value', + ChecksumSHA256: 'sha256-value', + ETag: 'etag-value', + }, + }, + }) + }) +}) + +describe('S3ProtocolHandler multipart completion regressions', () => { + it('preserves ChecksumCRC32C when completing multipart uploads', async () => { + const backend = { + completeMultipartUpload: vi.fn().mockResolvedValue({ + version: 'version-1', + ChecksumCRC32: 'crc32-value', + ChecksumCRC32C: 'crc32c-value', + ChecksumSHA1: 'sha1-value', + ChecksumSHA256: 'sha256-value', + ETag: 'etag-value', + }), + headObject: vi.fn().mockResolvedValue({ + cacheControl: '', + contentLength: 1, + size: 1, + mimetype: 'text/plain', + eTag: 'etag-value', + lastModified: new Date('2026-04-07T00:00:00.000Z'), + }), + } + const superUserDb = { + findMultipartUpload: vi.fn().mockResolvedValue({ + version: 'version-1', + user_metadata: null, + metadata: null, + }), + deleteMultipartUpload: vi.fn().mockResolvedValue(undefined), + } + const storage = { + backend, + db: { + asSuperUser: vi.fn(() => superUserDb), + }, + location: { + getKeyLocation: vi.fn().mockReturnValue('tenant-id/bucket/object.txt'), + }, + } + + const completeUploadResult = {} as Awaited> + + vi.spyOn(Uploader.prototype, 'canUpload').mockResolvedValue(undefined) + vi.spyOn(Uploader.prototype, 'completeUpload').mockResolvedValue(completeUploadResult) + + const handler = new S3ProtocolHandler(storage as never, 'tenant-id', 'owner-id') + const response = await handler.completeMultiPartUpload({ + Bucket: 'bucket', + Key: 'object.txt', + UploadId: 'upload-id', + MultipartUpload: { + Parts: [{ PartNumber: 1, ETag: 'part-etag' }], + }, + }) + + expect(response).toMatchObject({ + responseBody: { + CompleteMultipartUploadResult: { + ChecksumCRC32: 'crc32-value', + ChecksumCRC32C: 'crc32c-value', + ChecksumSHA1: 'sha1-value', + ChecksumSHA256: 'sha256-value', + ETag: 'etag-value', + }, + }, + }) + }) +}) + +describe('S3ProtocolHandler headObject validation', () => { + it('reports Key as the missing parameter for headObject', async () => { + const handler = new S3ProtocolHandler({} as never, 'tenant-id') + const missingKeyCommand = { Bucket: 'bucket' } as Parameters[0] + + await expect(handler.headObject(missingKeyCommand)).rejects.toMatchObject({ + message: 'Missing Required Parameter Key', + }) + }) + + it('reports Key as the missing parameter for dbHeadObject', async () => { + const handler = new S3ProtocolHandler({} as never, 'tenant-id') + const missingKeyCommand = { + Bucket: 'bucket', + } as Parameters[0] + + await expect(handler.dbHeadObject(missingKeyCommand)).rejects.toMatchObject({ + message: 'Missing Required Parameter Key', + }) + }) +}) diff --git a/src/http/routes/s3/router.ts b/src/http/routes/s3/router.ts index cd7bbb8e0..172bd7ec3 100644 --- a/src/http/routes/s3/router.ts +++ b/src/http/routes/s3/router.ts @@ -1,27 +1,28 @@ -import { FastifyRequest } from 'fastify' -import { FromSchema, JSONSchema } from 'json-schema-to-ts' +import { Storage } from '@storage/storage' import type { ValidateFunction } from 'ajv' import Ajv from 'ajv' -import { Storage } from '@storage/storage' -import { default as CreateBucket } from './commands/create-bucket' -import { default as ListBucket } from './commands/list-buckets' -import { default as ListObjects } from './commands/list-objects' -import { default as GetObject } from './commands/get-object' +import { JTDDataType } from 'ajv/dist/jtd' +import fastUri from 'fast-uri' +import { FastifyRequest } from 'fastify' +import { FromSchema, JSONSchema } from 'json-schema-to-ts' +import { default as AbortMultiPartUpload } from './commands/abort-multipart-upload' import { default as CompleteMultipartUpload } from './commands/complete-multipart-upload' -import { default as DeleteBucket } from './commands/delete-bucket' +import { default as CopyObject } from './commands/copy-object' +import { default as CreateBucket } from './commands/create-bucket' import { default as CreateMultipartUpload } from './commands/create-multipart-upload' -import { default as UploadPart } from './commands/upload-part' -import { default as PutObject } from './commands/put-object' -import { default as HeadObject } from './commands/head-object' +import { default as DeleteBucket } from './commands/delete-bucket' import { default as DeleteObject } from './commands/delete-object' -import { default as AbortMultiPartUpload } from './commands/abort-multipart-upload' import { default as GetBucket } from './commands/get-bucket' +import { default as GetObject } from './commands/get-object' import { default as HeadBucket } from './commands/head-bucket' -import { default as CopyObject } from './commands/copy-object' +import { default as HeadObject } from './commands/head-object' +import { default as ListBucket } from './commands/list-buckets' import { default as ListMultipartUploads } from './commands/list-multipart-uploads' +import { default as ListObjects } from './commands/list-objects' import { default as ListParts } from './commands/list-parts' +import { default as PutObject } from './commands/put-object' +import { default as UploadPart } from './commands/upload-part' import { default as UploadPartCopy } from './commands/upload-part-copy' -import { JTDDataType } from 'ajv/dist/jtd' export type Context = { storage: Storage @@ -65,7 +66,7 @@ export type Schema< Q extends JSONSchema = JSONSchema, H extends JSONSchema = JSONSchema, P extends JSONSchema = JSONSchema, - B extends JSONSchema = JSONSchema + B extends JSONSchema = JSONSchema, > = { summary?: string Querystring?: Q @@ -86,7 +87,7 @@ export type RequestInput< [key in keyof S]: S[key] extends JSONSchema ? FromSchema : undefined } = { [key in keyof S]: S[key] extends JSONSchema ? FromSchema : undefined - } + }, > = { Querystring: A['Querystring'] Headers: A['Headers'] @@ -99,15 +100,23 @@ type Handler = ( ctx: Context ) => Promise +export type QuerystringMatch = { + key: string + value: string | undefined +} + +export type RouteQuery = Record + type Route = { method: HTTPMethod type?: string path: string - querystringMatches: { key: string; value: string }[] + querystringMatches: QuerystringMatch[] headersMatches: string[] handler?: Handler schema: S disableContentTypeParser?: boolean + allowEmptyJsonBody?: boolean acceptMultiformData?: boolean operation: string compiledSchema: () => ValidateFunction> @@ -115,6 +124,7 @@ type Route = { interface RouteOptions { disableContentTypeParser?: boolean + allowEmptyJsonBody?: boolean acceptMultiformData?: boolean operation: string schema: S @@ -128,7 +138,7 @@ export class Router { coerceTypes: 'array', useDefaults: true, removeAdditional: true, - uriResolver: require('fast-uri'), + uriResolver: fastUri, addUsedSchema: false, allErrors: false, }) @@ -150,7 +160,8 @@ export class Router { Body?: JSONSchema } = {} - const { schema, disableContentTypeParser, acceptMultiformData, operation } = options + const { schema, disableContentTypeParser, allowEmptyJsonBody, acceptMultiformData, operation } = + options if (schema.Params) { schemaToCompile.Params = schema.Params @@ -198,10 +209,11 @@ export class Router { path: normalizedUrl, querystringMatches: query, headersMatches: headers, - schema: schema, + schema, compiledSchema: () => this.ajv.getSchema(method + url) as ValidateFunction>, handler: handler as Handler, disableContentTypeParser, + allowEmptyJsonBody, acceptMultiformData, operation, type: options.type, @@ -236,7 +248,7 @@ export class Router { this.registerRoute('head', url, options, handler as any) } - parseQueryMatch(query: string) { + parseQueryMatch(query: string): QuerystringMatch { const [key, value] = query.split('=') return { key, value } } @@ -246,9 +258,9 @@ export class Router { const headers = queryString.split('|').splice(1) if (queries.length === 0) { - return { query: [{ key: '*', value: '*' }], headers: headers } + return { query: [{ key: '*', value: '*' }], headers } } - return { query: queries.map(this.parseQueryMatch), headers: headers } + return { query: queries.map(this.parseQueryMatch), headers } } routes() { @@ -257,7 +269,7 @@ export class Router { matchRoute( route: Route, - match: { query: Record; headers: Record; type?: string } + match: { query: RouteQuery; headers: Record; type?: string } ) { const isOfType = match.type ? match.type === route.type : route.type === undefined @@ -283,37 +295,52 @@ export class Router { const headerValue = headerParts[1] const matchHeaderName = received[headerName] !== undefined - const matchHeaderValue = headerValue ? received[headerName].startsWith(headerValue) : true + const matchHeaderValue = headerValue ? received[headerName]?.startsWith(headerValue) : true return matchHeaderName && matchHeaderValue }) } - protected matchQueryString( - matches: { key: string; value: string }[], - received?: Record - ) { - const keys = Object.keys(received || {}) - if (keys.length === 0 || !received) { - return matches.find((m) => m.key === '*') + protected matchQueryString(matches: QuerystringMatch[], received?: RouteQuery) { + let hasWildcard = false + for (const match of matches) { + if (match.key === '*') { + hasWildcard = true + break + } } - const foundMatches = matches.every((m) => { - const key = Object.keys(received).find((k) => k === m.key) - return ( - (m.key === key && m.value !== undefined && m.value === received[m.key]) || - (m.key === key && m.value === undefined) - ) - }) + if (!received) { + return hasWildcard + } + + let hasReceivedQuery = false + for (const key in received) { + if (Object.prototype.hasOwnProperty.call(received, key)) { + hasReceivedQuery = true + break + } + } - if (foundMatches) { - return true + if (!hasReceivedQuery) { + return hasWildcard } - if (!foundMatches && matches.find((m) => m.key === '*')) { - return true + for (const match of matches) { + if (match.key === '*') { + continue + } + + if (!Object.prototype.hasOwnProperty.call(received, match.key)) { + return hasWildcard + } + + if (match.value !== undefined && match.value !== received[match.key]) { + return hasWildcard + } } - return false + + return true } } diff --git a/src/http/routes/tus/index.ts b/src/http/routes/tus/index.ts index 008e69486..6892d6e14 100644 --- a/src/http/routes/tus/index.ts +++ b/src/http/routes/tus/index.ts @@ -1,32 +1,33 @@ +import * as https from 'node:https' +import { S3Client } from '@aws-sdk/client-s3' +import { PubSub, TenantConnection } from '@internal/database' +import { ERRORS } from '@internal/errors' +import { createAgent } from '@internal/http' +import { logSchema } from '@internal/monitoring' +import { NodeHttpHandler } from '@smithy/node-http-handler' +import { getFileSizeLimit } from '@storage/limits' +import { AlsMemoryKV, FileStore, LockNotifier, PgLocker, UploadId } from '@storage/protocols/tus' +import { S3Locker } from '@storage/protocols/tus/s3-locker' +import { Storage } from '@storage/storage' +import { S3Store } from '@tus/s3-store' +import { DataStore, Server, ServerOptions } from '@tus/server' import { FastifyBaseLogger, FastifyInstance } from 'fastify' import fastifyPlugin from 'fastify-plugin' import * as http from 'http' -import { ServerOptions, DataStore, Server } from '@tus/server' -import { getFileSizeLimit } from '@storage/limits' -import { Storage } from '@storage/storage' -import { jwt, storage, db, dbSuperUser } from '../../plugins' +import type { ServerRequest as Request } from 'srvx' import { getConfig } from '../../../config' -import { FileStore, LockNotifier, PgLocker, UploadId, AlsMemoryKV } from '@storage/protocols/tus' +import { db, dbSuperUser, jwt, storage } from '../../plugins' +import { ROUTE_OPERATIONS } from '../operations' import { + generateUrl, + getFileIdFromRequest, namingFunction, onCreate, - onResponseError, onIncomingRequest, + onResponseError, onUploadFinish, - generateUrl, - getFileIdFromRequest, SIGNED_URL_SUFFIX, } from './lifecycle' -import { TenantConnection, PubSub } from '@internal/database' -import { S3Store } from '@tus/s3-store' -import { NodeHttpHandler } from '@smithy/node-http-handler' -import { ROUTE_OPERATIONS } from '../operations' -import * as https from 'node:https' -import { createAgent } from '@internal/http' -import type { ServerRequest as Request } from 'srvx' -import { S3Locker } from '@storage/protocols/tus/s3-locker' -import { S3Client } from '@aws-sdk/client-s3' -import { ERRORS } from '@internal/errors' const { storageS3MaxSockets, @@ -94,7 +95,7 @@ function createTusServer( datastore: DataStore } = { path: tusPath, - datastore: datastore, + datastore, disableTerminationForFinishedUploads: true, locker: (rawReq: Request) => { const req = rawReq.node?.req as MultiPartRequest @@ -133,13 +134,13 @@ function createTusServer( throw ERRORS.InternalError(undefined, 'Unsupported TUS locker type') } }, - namingFunction: namingFunction, + namingFunction, onUploadCreate: onCreate, - onUploadFinish: onUploadFinish, - onIncomingRequest: onIncomingRequest, - generateUrl: generateUrl, - getFileIdFromRequest: getFileIdFromRequest, - onResponseError: onResponseError, + onUploadFinish, + onIncomingRequest: (req, id) => onIncomingRequest(req, id, datastore), + generateUrl, + getFileIdFromRequest, + onResponseError, respectForwardedHeaders: true, allowedHeaders: ['Authorization', 'X-Upsert', 'Upload-Expires', 'ApiKey', 'x-signature'], maxSize: async (rawReq, uploadId) => { @@ -174,15 +175,22 @@ function createTusServer( export default async function routes(fastify: FastifyInstance) { const lockNotifier = new LockNotifier(PubSub) - await lockNotifier.subscribe() + await lockNotifier.start() const agent = createAgent('s3_tus', { maxSockets: storageS3MaxSockets, }) agent.monitor() - fastify.addHook('onClose', () => { + fastify.addHook('onClose', async () => { agent.close() + + await lockNotifier.stop().catch((e) => { + logSchema.error(fastify.log, 'Failed to stop TUS lock notifier', { + type: 'tus', + error: e, + }) + }) }) const tusServer = createTusServer(lockNotifier, agent) diff --git a/src/http/routes/tus/lifecycle.ts b/src/http/routes/tus/lifecycle.ts index 536590b0d..e8c4f497a 100644 --- a/src/http/routes/tus/lifecycle.ts +++ b/src/http/routes/tus/lifecycle.ts @@ -1,17 +1,18 @@ -import http from 'http' -import { BaseLogger } from 'pino' -import { Upload } from '@tus/server' -import { randomUUID } from 'crypto' import { TenantConnection } from '@internal/database' import { ERRORS, isRenderableError } from '@internal/errors' +import { UploadId } from '@storage/protocols/tus' import { Storage } from '@storage/storage' import { Uploader, validateMimeType } from '@storage/uploader' -import { UploadId } from '@storage/protocols/tus' +import { DataStore, Metadata, Upload } from '@tus/server' +import { randomUUID } from 'crypto' +import http from 'http' +import { BaseLogger } from 'pino' import type { ServerRequest as Request } from 'srvx' import { getConfig } from '../../../config' -const { storageS3Bucket, tusPath, requestAllowXForwardedPrefix } = getConfig() +const { storageS3Bucket, tusPath, requestAllowXForwardedPrefix, storagePublicUrl } = getConfig() +const parsedPublicUrl = storagePublicUrl ? new URL(storagePublicUrl) : undefined const reExtractFileID = /([^/]+)\/?$/ export const SIGNED_URL_SUFFIX = '/sign' @@ -44,7 +45,7 @@ export type MultiPartRequest = http.IncomingMessage & { /** * Runs on every TUS incoming request */ -export async function onIncomingRequest(rawReq: Request, id: string) { +export async function onIncomingRequest(rawReq: Request, id: string, datastore: DataStore) { const req = getNodeRequest(rawReq) const res = rawReq.node?.res as http.ServerResponse @@ -52,12 +53,7 @@ export async function onIncomingRequest(rawReq: Request, id: string) { throw ERRORS.InternalError(undefined, 'Response object is missing') } - if (!res) { - throw ERRORS.InternalError(undefined, 'Response object is missing') - } - res.on('finish', () => { - console.log('Tus request finished') req.upload.db.dispose().catch((e) => { req.log.error({ error: e }, 'Error disposing db connection') }) @@ -96,11 +92,53 @@ export async function onIncomingRequest(rawReq: Request, id: string) { req.upload.storage.location ) + let contentType: string | undefined + let contentLength: number | undefined + let rawMetadata: string | null | undefined + + if (req.method === 'POST') { + const uploadMetadataHeader = req.headers['upload-metadata'] + if (uploadMetadataHeader && typeof uploadMetadataHeader === 'string') { + try { + const parsedMetadata = Metadata.parse(uploadMetadataHeader) + contentType = parsedMetadata?.contentType ?? undefined + rawMetadata = parsedMetadata?.metadata + } catch (e) { + req.log.warn({ error: e }, 'Failed to parse upload metadata') + throw ERRORS.InvalidParameter('upload-metadata', { + error: e as Error, + message: 'Invalid Upload-Metadata header', + }) + } + } + const uploadLength = req.headers['upload-length'] + contentLength = uploadLength ? Number(uploadLength) : undefined + } else { + const upload = await datastore.getUpload(id) + contentType = upload.metadata?.contentType ?? undefined + contentLength = upload.size ?? undefined + rawMetadata = upload.metadata?.metadata + } + + let customMd: Record | undefined + if (rawMetadata) { + try { + customMd = JSON.parse(rawMetadata) + } catch (e) { + req.log.warn({ error: e }, 'Failed to parse user metadata') + } + } + await uploader.canUpload({ owner: req.upload.owner, bucketId: uploadID.bucket, objectName: uploadID.objectName, - isUpsert: isUpsert, + isUpsert, + userMetadata: customMd, + metadata: { + mimetype: contentType, + contentLength, + }, }) } @@ -117,22 +155,27 @@ export function generateUrl( throw ERRORS.InvalidParameter('url') } - if (!req.url) { - throw ERRORS.InvalidParameter('url') + if (parsedPublicUrl) { + proto = parsedPublicUrl.protocol.replace(':', '') + host = parsedPublicUrl.host } + + // Force https in production. This overrides both forwarded headers and + // STORAGE_PUBLIC_URL - production deployments must use HTTPS. proto = process.env.NODE_ENV === 'production' ? 'https' : proto let basePath = path const forwardedPath = req.headers['x-forwarded-prefix'] if (requestAllowXForwardedPrefix && typeof forwardedPath === 'string') { - basePath = forwardedPath + path + // Remove trailing slash from forwardedPath to avoid double slashes + basePath = forwardedPath.replace(/\/+$/, '') + path } const isSigned = req.url?.endsWith(SIGNED_URL_SUFFIX) const fullPath = isSigned ? `${basePath}${SIGNED_URL_SUFFIX}` : basePath - if (req.headers['x-forwarded-host']) { + if (!parsedPublicUrl && req.headers['x-forwarded-host']) { const port = req.headers['x-forwarded-port'] if (typeof port === 'string' && port && !['443', '80'].includes(port)) { @@ -179,10 +222,6 @@ export function namingFunction(rawReq: Request, metadata?: Record +} + +export default async function routes(fastify: FastifyInstance) { + fastify.post( + '/CreateVectorBucket', + { + config: { + operation: { type: ROUTE_OPERATIONS.CREATE_VECTOR_BUCKET }, + }, + schema: { + ...createVectorBucket, + tags: ['vector'], + }, + }, + async (request, response) => { + if (!request.s3Vector) { + throw ERRORS.FeatureNotEnabled('vectorStore', 'Vector service not configured') + } + + await request.s3Vector.createBucket(request.body.vectorBucketName) + + return response.send() + } + ) +} diff --git a/src/http/routes/vector/create-index.ts b/src/http/routes/vector/create-index.ts new file mode 100644 index 000000000..1b9959382 --- /dev/null +++ b/src/http/routes/vector/create-index.ts @@ -0,0 +1,66 @@ +import { ERRORS } from '@internal/errors' +import { FastifyInstance } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import { AuthenticatedRequest } from '../../types' +import { ROUTE_OPERATIONS } from '../operations' + +const createVectorIndex = { + type: 'object', + body: { + type: 'object', + properties: { + dataType: { type: 'string', enum: ['float32'] }, + dimension: { type: 'number', minimum: 1, maximum: 4096 }, + distanceMetric: { type: 'string', enum: ['cosine', 'euclidean'] }, + indexName: { + type: 'string', + minLength: 3, + maxLength: 45, + pattern: '^[a-z0-9](?:[a-z0-9.-]{1,61})?[a-z0-9]$', + description: + '3-63 chars, lowercase letters, numbers, hyphens, dots; must start/end with letter or number. Must be unique within the vector bucket.', + }, + metadataConfiguration: { + type: 'object', + required: ['nonFilterableMetadataKeys'], + properties: { + nonFilterableMetadataKeys: { + type: 'array', + items: { type: 'string' }, + }, + }, + }, + vectorBucketName: { type: 'string' }, + }, + required: ['dataType', 'dimension', 'distanceMetric', 'indexName', 'vectorBucketName'], + }, + summary: 'Create a vector index', +} as const + +interface createVectorIndexRequest extends AuthenticatedRequest { + Body: FromSchema<(typeof createVectorIndex)['body']> +} + +export default async function routes(fastify: FastifyInstance) { + fastify.post( + '/CreateIndex', + { + config: { + operation: { type: ROUTE_OPERATIONS.CREATE_VECTOR_INDEX }, + }, + schema: { + ...createVectorIndex, + tags: ['vector'], + }, + }, + async (request, response) => { + if (!request.s3Vector) { + throw ERRORS.FeatureNotEnabled('vectorStore', 'Vector service not configured') + } + + await request.s3Vector.createVectorIndex(request.body) + + return response.send() + } + ) +} diff --git a/src/http/routes/vector/delete-bucket.ts b/src/http/routes/vector/delete-bucket.ts new file mode 100644 index 000000000..31d88dfdf --- /dev/null +++ b/src/http/routes/vector/delete-bucket.ts @@ -0,0 +1,45 @@ +import { ERRORS } from '@internal/errors' +import { FastifyInstance } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import { AuthenticatedRequest } from '../../types' +import { ROUTE_OPERATIONS } from '../operations' + +const deleteVectorBucket = { + type: 'object', + body: { + type: 'object', + properties: { + vectorBucketName: { type: 'string' }, + }, + required: ['vectorBucketName'], + }, + summary: 'Create a vector bucket', +} as const + +interface deleteVectorIndexRequest extends AuthenticatedRequest { + Body: FromSchema<(typeof deleteVectorBucket)['body']> +} + +export default async function routes(fastify: FastifyInstance) { + fastify.post( + '/DeleteVectorBucket', + { + config: { + operation: { type: ROUTE_OPERATIONS.DELETE_VECTOR_BUCKET }, + }, + schema: { + ...deleteVectorBucket, + tags: ['vector'], + }, + }, + async (request, response) => { + if (!request.s3Vector) { + throw ERRORS.FeatureNotEnabled('vectorStore', 'Vector service not configured') + } + + await request.s3Vector.deleteBucket(request.body.vectorBucketName) + + return response.send() + } + ) +} diff --git a/src/http/routes/vector/delete-index.ts b/src/http/routes/vector/delete-index.ts new file mode 100644 index 000000000..266e4ad50 --- /dev/null +++ b/src/http/routes/vector/delete-index.ts @@ -0,0 +1,53 @@ +import { ERRORS } from '@internal/errors' +import { FastifyInstance } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import { AuthenticatedRequest } from '../../types' +import { ROUTE_OPERATIONS } from '../operations' + +const deleteVectorIndex = { + type: 'object', + body: { + type: 'object', + properties: { + indexName: { + type: 'string', + minLength: 3, + maxLength: 45, + pattern: '^[a-z0-9](?:[a-z0-9.-]{1,61})?[a-z0-9]$', + description: + '3-63 chars, lowercase letters, numbers, hyphens, dots; must start/end with letter or number. Must be unique within the vector bucket.', + }, + vectorBucketName: { type: 'string' }, + }, + required: ['indexName', 'vectorBucketName'], + }, + summary: 'Delete a vector index', +} as const + +interface deleteVectorIndexRequest extends AuthenticatedRequest { + Body: FromSchema<(typeof deleteVectorIndex)['body']> +} + +export default async function routes(fastify: FastifyInstance) { + fastify.post( + '/DeleteIndex', + { + config: { + operation: { type: ROUTE_OPERATIONS.DELETE_VECTOR_INDEX }, + }, + schema: { + ...deleteVectorIndex, + tags: ['vector'], + }, + }, + async (request, response) => { + if (!request.s3Vector) { + throw ERRORS.FeatureNotEnabled('vectorStore', 'Vector service not configured') + } + + await request.s3Vector.deleteIndex(request.body) + + return response.send() + } + ) +} diff --git a/src/http/routes/vector/delete-vectors.ts b/src/http/routes/vector/delete-vectors.ts new file mode 100644 index 000000000..6d02192dd --- /dev/null +++ b/src/http/routes/vector/delete-vectors.ts @@ -0,0 +1,50 @@ +import { ERRORS } from '@internal/errors' +import { FastifyInstance } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import { AuthenticatedRequest } from '../../types' +import { ROUTE_OPERATIONS } from '../operations' + +const deleteVector = { + body: { + type: 'object', + properties: { + vectorBucketName: { type: 'string' }, + indexName: { type: 'string' }, + keys: { type: 'array', items: { type: 'string' } }, + }, + required: ['vectorBucketName', 'indexName', 'keys'], + }, + summary: 'Delete vectors from an index', +} as const + +interface deleteVectorRequest extends AuthenticatedRequest { + Body: FromSchema<(typeof deleteVector)['body']> +} + +export default async function routes(fastify: FastifyInstance) { + fastify.post( + '/DeleteVectors', + { + config: { + operation: { type: ROUTE_OPERATIONS.DELETE_VECTORS }, + }, + schema: { + ...deleteVector, + tags: ['vector'], + }, + }, + async (request, response) => { + if (!request.s3Vector) { + throw ERRORS.FeatureNotEnabled('vectorStore', 'Vector service not configured') + } + + await request.s3Vector.deleteVectors({ + vectorBucketName: request.body.vectorBucketName, + indexName: request.body.indexName, + keys: request.body.keys, + }) + + return response.send() + } + ) +} diff --git a/src/http/routes/vector/get-bucket.ts b/src/http/routes/vector/get-bucket.ts new file mode 100644 index 000000000..cfc3611c7 --- /dev/null +++ b/src/http/routes/vector/get-bucket.ts @@ -0,0 +1,45 @@ +import { ERRORS } from '@internal/errors' +import { FastifyInstance } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import { AuthenticatedRequest } from '../../types' +import { ROUTE_OPERATIONS } from '../operations' + +const getVectorBucket = { + type: 'object', + body: { + type: 'object', + properties: { + vectorBucketName: { type: 'string' }, + }, + required: ['vectorBucketName'], + }, + summary: 'Create a vector bucket', +} as const + +interface getVectorBucketRequest extends AuthenticatedRequest { + Body: FromSchema<(typeof getVectorBucket)['body']> +} + +export default async function routes(fastify: FastifyInstance) { + fastify.post( + '/GetVectorBucket', + { + config: { + operation: { type: ROUTE_OPERATIONS.GET_VECTOR_BUCKET }, + }, + schema: { + ...getVectorBucket, + tags: ['vector'], + }, + }, + async (request, response) => { + if (!request.s3Vector) { + throw ERRORS.FeatureNotEnabled('vectorStore', 'Vector service not configured') + } + + const bucketResult = await request.s3Vector.getBucket(request.body) + + return response.send(bucketResult) + } + ) +} diff --git a/src/http/routes/vector/get-index.ts b/src/http/routes/vector/get-index.ts new file mode 100644 index 000000000..89f5b6272 --- /dev/null +++ b/src/http/routes/vector/get-index.ts @@ -0,0 +1,64 @@ +import { ERRORS } from '@internal/errors' +import { FastifyInstance } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import { AuthenticatedRequest } from '../../types' +import { ROUTE_OPERATIONS } from '../operations' + +const getVectorIndex = { + type: 'object', + body: { + type: 'object', + properties: { + vectorBucketName: { type: 'string' }, + indexName: { + type: 'string', + minLength: 3, + maxLength: 45, + pattern: '^[a-z0-9](?:[a-z0-9.-]{1,61})?[a-z0-9]$', + description: + '3-63 chars, lowercase letters, numbers, hyphens, dots; must start/end with letter or number. Must be unique within the vector bucket.', + }, + }, + required: ['vectorBucketName', 'indexName'], + }, + summary: 'Get a vector index', +} as const + +interface getVectorIndexRequest extends AuthenticatedRequest { + Body: FromSchema<(typeof getVectorIndex)['body']> +} + +export default async function routes(fastify: FastifyInstance) { + fastify.post( + '/GetIndex', + { + config: { + operation: { type: ROUTE_OPERATIONS.GET_VECTOR_INDEX }, + }, + schema: { + ...getVectorIndex, + tags: ['vector'], + }, + }, + async (request, response) => { + if (!request.s3Vector) { + throw ERRORS.FeatureNotEnabled('vectorStore', 'Vector service not configured') + } + + const indexResult = await request.s3Vector.getIndex({ + vectorBucketName: request.body.vectorBucketName, + indexName: request.body.indexName, + }) + + return response.send({ + ...indexResult, + index: { + ...indexResult.index, + creationTime: indexResult.index?.creationTime + ? Math.floor(indexResult.index?.creationTime?.getTime() / 1000) + : undefined, + }, + }) + } + ) +} diff --git a/src/http/routes/vector/get-vectors.ts b/src/http/routes/vector/get-vectors.ts new file mode 100644 index 000000000..18bf97bd4 --- /dev/null +++ b/src/http/routes/vector/get-vectors.ts @@ -0,0 +1,49 @@ +import { ERRORS } from '@internal/errors' +import { FastifyInstance } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import { AuthenticatedRequest } from '../../types' +import { ROUTE_OPERATIONS } from '../operations' + +const getVectors = { + type: 'object', + body: { + type: 'object', + properties: { + indexName: { type: 'string' }, + keys: { type: 'array', items: { type: 'string' } }, + returnData: { type: 'boolean', default: false }, + returnMetadata: { type: 'boolean', default: false }, + vectorBucketName: { type: 'string' }, + }, + required: ['indexName', 'keys', 'vectorBucketName'], + }, + summary: 'Returns vector attributes', +} as const + +interface getVectorsRequest extends AuthenticatedRequest { + Body: FromSchema<(typeof getVectors)['body']> +} + +export default async function routes(fastify: FastifyInstance) { + fastify.post( + '/GetVectors', + { + config: { + operation: { type: ROUTE_OPERATIONS.GET_VECTORS }, + }, + schema: { + ...getVectors, + tags: ['vector'], + }, + }, + async (request, response) => { + if (!request.s3Vector) { + throw ERRORS.FeatureNotEnabled('vectorStore', 'Vector service not configured') + } + + const indexResult = await request.s3Vector.getVectors(request.body) + + return response.send(indexResult) + } + ) +} diff --git a/src/http/routes/vector/index.ts b/src/http/routes/vector/index.ts new file mode 100644 index 000000000..3c9c9f41f --- /dev/null +++ b/src/http/routes/vector/index.ts @@ -0,0 +1,76 @@ +import { SignatureV4Service } from '@storage/protocols/s3' +import { FastifyInstance } from 'fastify' +import { getConfig } from '../../../config' +import { setErrorHandler } from '../../error-handler' +import { + dbSuperUser, + enforceJwtRole, + jwt, + requireTenantFeature, + s3vector, + signatureV4, +} from '../../plugins' +import createVectorBucket from './create-bucket' +import createVectorIndex from './create-index' +import deleteVectorBucket from './delete-bucket' +import deleteVectorIndex from './delete-index' +import deleteVectors from './delete-vectors' +import getVectorBucket from './get-bucket' +import getIndex from './get-index' +import getVectors from './get-vectors' +import listVectorBuckets from './list-buckets' +import listIndexes from './list-indexes' +import listVectors from './list-vectors' +import putVectors from './put-vectors' +import queryVectors from './query-vectors' + +export default async function routes(fastify: FastifyInstance) { + const { dbServiceRole, vectorEnabled, isMultitenant } = getConfig() + + if (!vectorEnabled && !isMultitenant) { + return + } + + fastify.register(async function authenticated(fastify) { + if (!vectorEnabled && isMultitenant) { + fastify.register(requireTenantFeature('vectorBuckets')) + } + + fastify.register(signatureV4, { + service: SignatureV4Service.S3VECTORS, + allowBodyHash: true, + skipIfJwtToken: true, + }) + + fastify.register(jwt, { + skipIfAlreadyAuthenticated: true, + }) + + fastify.register(enforceJwtRole, { + roles: [dbServiceRole], + }) + + fastify.register(dbSuperUser) + fastify.register(s3vector) + + fastify.register(createVectorIndex) + fastify.register(deleteVectorIndex) + fastify.register(listIndexes) + fastify.register(getIndex) + + fastify.register(createVectorBucket) + fastify.register(deleteVectorBucket) + fastify.register(listVectorBuckets) + fastify.register(getVectorBucket) + + fastify.register(putVectors) + fastify.register(queryVectors) + fastify.register(deleteVectors) + fastify.register(listVectors) + fastify.register(getVectors) + + setErrorHandler(fastify, { + respectStatusCode: true, + }) + }) +} diff --git a/src/http/routes/vector/list-buckets.ts b/src/http/routes/vector/list-buckets.ts new file mode 100644 index 000000000..31ee59736 --- /dev/null +++ b/src/http/routes/vector/list-buckets.ts @@ -0,0 +1,46 @@ +import { ERRORS } from '@internal/errors' +import { FastifyInstance } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import { AuthenticatedRequest } from '../../types' +import { ROUTE_OPERATIONS } from '../operations' + +const listBucket = { + type: 'object', + body: { + type: 'object', + properties: { + maxResults: { type: 'number', minimum: 1, maximum: 500, default: 500 }, + nextToken: { type: 'string' }, + prefix: { type: 'string' }, + }, + }, + summary: 'List vector buckets', +} as const + +interface listBucketRequest extends AuthenticatedRequest { + Body: FromSchema<(typeof listBucket)['body']> +} + +export default async function routes(fastify: FastifyInstance) { + fastify.post( + '/ListVectorBuckets', + { + config: { + operation: { type: ROUTE_OPERATIONS.LIST_VECTOR_BUCKETS }, + }, + schema: { + ...listBucket, + tags: ['vector'], + }, + }, + async (request, response) => { + if (!request.s3Vector) { + throw ERRORS.FeatureNotEnabled('vectorStore', 'Vector service not configured') + } + + const listBucketsResult = await request.s3Vector.listBuckets(request.body) + + return response.send(listBucketsResult) + } + ) +} diff --git a/src/http/routes/vector/list-indexes.ts b/src/http/routes/vector/list-indexes.ts new file mode 100644 index 000000000..c0703f313 --- /dev/null +++ b/src/http/routes/vector/list-indexes.ts @@ -0,0 +1,51 @@ +import { ERRORS } from '@internal/errors' +import { FastifyInstance } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import { AuthenticatedRequest } from '../../types' +import { ROUTE_OPERATIONS } from '../operations' + +const listIndex = { + type: 'object', + body: { + type: 'object', + properties: { + vectorBucketName: { type: 'string' }, + maxResults: { type: 'number', minimum: 1, maximum: 500, default: 500 }, + nextToken: { type: 'string' }, + prefix: { type: 'string' }, + }, + required: ['vectorBucketName'], + }, + summary: 'List indexes in a vector bucket', +} as const + +interface listIndexRequest extends AuthenticatedRequest { + Body: FromSchema<(typeof listIndex)['body']> +} + +export default async function routes(fastify: FastifyInstance) { + fastify.post( + '/ListIndexes', + { + config: { + operation: { type: ROUTE_OPERATIONS.LIST_VECTOR_INDEXES }, + }, + schema: { + ...listIndex, + tags: ['vector'], + }, + }, + async (request, response) => { + if (!request.s3Vector) { + throw ERRORS.FeatureNotEnabled('vectorStore', 'Vector service not configured') + } + + const indexResult = await request.s3Vector.listIndexes({ + ...request.body, + vectorBucketName: request.body.vectorBucketName, + }) + + return response.send(indexResult) + } + ) +} diff --git a/src/http/routes/vector/list-vectors.ts b/src/http/routes/vector/list-vectors.ts new file mode 100644 index 000000000..1a8d0980d --- /dev/null +++ b/src/http/routes/vector/list-vectors.ts @@ -0,0 +1,60 @@ +import { ERRORS } from '@internal/errors' +import { FastifyInstance } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import { AuthenticatedRequest } from '../../types' +import { ROUTE_OPERATIONS } from '../operations' + +const listVectors = { + type: 'object', + body: { + type: 'object', + properties: { + vectorBucketName: { type: 'string' }, + indexArn: { type: 'string' }, + indexName: { + type: 'string', + minLength: 3, + maxLength: 45, + pattern: '^[a-z0-9](?:[a-z0-9.-]{1,61})?[a-z0-9]$', + description: + '3-63 chars, lowercase letters, numbers, hyphens, dots; must start/end with letter or number. Must be unique within the vector bucket.', + }, + maxResults: { type: 'number', minimum: 1, maximum: 500 }, + nextToken: { type: 'string' }, + returnData: { type: 'boolean' }, + returnMetadata: { type: 'boolean' }, + segmentCount: { type: 'number', minimum: 1, maximum: 16 }, + segmentIndex: { type: 'number', minimum: 0, maximum: 15 }, + }, + required: ['vectorBucketName', 'indexName'], + }, + summary: 'List vectors in a vector index', +} as const + +interface listVectorsRequest extends AuthenticatedRequest { + Body: FromSchema<(typeof listVectors)['body']> +} + +export default async function routes(fastify: FastifyInstance) { + fastify.post( + '/ListVectors', + { + config: { + operation: { type: ROUTE_OPERATIONS.LIST_VECTORS }, + }, + schema: { + ...listVectors, + tags: ['vector'], + }, + }, + async (request, response) => { + if (!request.s3Vector) { + throw ERRORS.FeatureNotEnabled('vectorStore', 'Vector service not configured') + } + + const indexResult = await request.s3Vector.listVectors(request.body) + + return response.send(indexResult) + } + ) +} diff --git a/src/http/routes/vector/put-vectors.ts b/src/http/routes/vector/put-vectors.ts new file mode 100644 index 000000000..caaba3958 --- /dev/null +++ b/src/http/routes/vector/put-vectors.ts @@ -0,0 +1,87 @@ +import { ERRORS } from '@internal/errors' +import { FastifyInstance } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import { AuthenticatedRequest } from '../../types' +import { ROUTE_OPERATIONS } from '../operations' + +const putVector = { + body: { + type: 'object', + properties: { + vectorBucketName: { type: 'string' }, + indexName: { + type: 'string', + minLength: 3, + maxLength: 45, + pattern: '^[a-z0-9](?:[a-z0-9.-]{1,61})?[a-z0-9]$', + description: + '3-63 chars, lowercase letters, numbers, hyphens, dots; must start/end with letter or number. Must be unique within the vector bucket.', + }, + vectors: { + type: 'array', + minItems: 1, + maxItems: 500, + items: { + type: 'object', + properties: { + data: { + type: 'object', + properties: { + float32: { type: 'array', items: { type: 'number' } }, + }, + required: ['float32'], + }, + metadata: { + type: 'object', + additionalProperties: { + oneOf: [{ type: 'string' }, { type: 'boolean' }, { type: 'number' }], + }, + }, + key: { type: 'string' }, + }, + required: ['data'], + }, + }, + }, + required: ['vectorBucketName', 'indexName', 'vectors'], + }, + summary: 'Put vectors into an index', +} as const + +interface putVectorRequest extends AuthenticatedRequest { + Body: FromSchema<(typeof putVector)['body']> +} + +export default async function routes(fastify: FastifyInstance) { + fastify.post( + '/PutVectors', + { + bodyLimit: 20 * 1024 * 1024, // 20 MB + config: { + operation: { type: ROUTE_OPERATIONS.PUT_VECTORS }, + }, + schema: { + ...putVector, + tags: ['vector'], + }, + }, + async (request, response) => { + if (!request.s3Vector) { + throw ERRORS.FeatureNotEnabled('vectorStore', 'Vector service not configured') + } + + const indexResult = await request.s3Vector.putVectors({ + vectorBucketName: request.body.vectorBucketName, + indexName: request.body.indexName, + vectors: request.body.vectors.map((v) => { + return { + ...v, + key: v.key || undefined, + } + }), + }) + + return response.send(indexResult) + } + ) +} diff --git a/src/http/routes/vector/query-vectors.ts b/src/http/routes/vector/query-vectors.ts new file mode 100644 index 000000000..d9d4276b3 --- /dev/null +++ b/src/http/routes/vector/query-vectors.ts @@ -0,0 +1,159 @@ +import { ERRORS } from '@internal/errors' +import Ajv from 'ajv' +import { FastifyInstance, FastifySchemaCompiler } from 'fastify' +import { FromSchema } from 'json-schema-to-ts' +import { AuthenticatedRequest } from '../../types' +import { ROUTE_OPERATIONS } from '../operations' + +const defs = { + $id: 'https://schemas.example.com/defs.json', + $defs: { + Primitive: { + anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }], + }, + FieldOperators: { + type: 'object', + // ensure at least one operator remains after removal + minProperties: 1, + // only allow keys that start with '$' + propertyNames: { pattern: '^\\$' }, + properties: { + $eq: { $ref: '#/$defs/Primitive' }, + $ne: { $ref: '#/$defs/Primitive' }, + $gt: { type: 'number' }, + $gte: { type: 'number' }, + $lt: { type: 'number' }, + $lte: { type: 'number' }, + $in: { type: 'array', minItems: 1, items: { $ref: '#/$defs/Primitive' } }, + $nin: { type: 'array', minItems: 1, items: { $ref: '#/$defs/Primitive' } }, + $exists: { type: 'boolean' }, + }, + additionalProperties: false, + }, + LogicalFilter: { + anyOf: [ + { + type: 'object', + properties: { + $and: { + type: 'array', + minItems: 1, + items: { $ref: '#/$defs/Filter' }, + }, + }, + required: ['$and'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + $or: { + type: 'array', + minItems: 1, + items: { $ref: '#/$defs/Filter' }, + }, + }, + required: ['$or'], + additionalProperties: false, + }, + ], + }, + Filter: { + anyOf: [ + { $ref: '#/$defs/LogicalFilter' }, + { + type: 'object', + additionalProperties: { + anyOf: [{ $ref: '#/$defs/Primitive' }, { $ref: '#/$defs/FieldOperators' }], + }, + }, + ], + }, + }, +} as const + +const queryVectorBody = { + $id: 'https://schemas.example.com/queryVectorBody.json', + type: 'object', + properties: { + filter: { $ref: 'https://schemas.example.com/defs.json#/$defs/Filter' }, + indexArn: { type: 'string' }, + indexName: { + type: 'string', + minLength: 3, + maxLength: 45, + pattern: '^[a-z0-9](?:[a-z0-9.-]{1,61})?[a-z0-9]$', + }, + queryVector: { + type: 'object', + properties: { + float32: { type: 'array', items: { type: 'number' } }, + }, + required: ['float32'], + additionalProperties: false, + }, + returnDistance: { type: 'boolean' }, + returnMetadata: { type: 'boolean' }, + topK: { type: 'number' }, + vectorBucketName: { type: 'string' }, + }, + required: ['vectorBucketName', 'indexName', 'queryVector', 'topK'], + additionalProperties: false, +} as const + +interface queryVectorRequest extends AuthenticatedRequest { + Body: FromSchema +} + +export default async function routes(fastify: FastifyInstance) { + const ajvNoRemoval = new Ajv({ + allErrors: true, + removeAdditional: false, // <- key bit + coerceTypes: false, + }) + + const perRouteValidator: FastifySchemaCompiler = ({ schema }) => { + const validate = ajvNoRemoval.compile(schema as object) + return (data) => { + const ok = validate(data) + if (ok) return { value: data } + return { error: new Error(JSON.stringify(validate.errors)) } + } + } + + ajvNoRemoval.addSchema(defs) + ajvNoRemoval.addSchema(queryVectorBody) + + fastify.post( + '/QueryVectors', + { + validatorCompiler: perRouteValidator, + config: { + operation: { type: ROUTE_OPERATIONS.QUERY_VECTORS }, + }, + schema: { + body: { $ref: 'https://schemas.example.com/queryVectorBody.json' }, + tags: ['vector'], + summary: 'Query vectors', + }, + }, + async (request, response) => { + if (!request.s3Vector) { + throw ERRORS.FeatureNotEnabled('vectorStore', 'Vector service not configured') + } + + const indexResult = await request.s3Vector.queryVectors({ + vectorBucketName: request.body.vectorBucketName, + indexName: request.body.indexName, + indexArn: request.body.indexArn, + queryVector: request.body.queryVector, + topK: request.body.topK, + filter: request.body.filter, + returnDistance: request.body.returnDistance, + returnMetadata: request.body.returnMetadata, + }) + + return response.send(indexResult) + } + ) +} diff --git a/src/http/schemas/index.ts b/src/http/schemas/index.ts index 3f21f8621..2d4ead280 100644 --- a/src/http/schemas/index.ts +++ b/src/http/schemas/index.ts @@ -1,2 +1,2 @@ -export * from './error' export * from './auth' +export * from './error' diff --git a/src/http/schemas/transformations.ts b/src/http/schemas/transformations.ts index 907dd154a..7ca76a2e2 100644 --- a/src/http/schemas/transformations.ts +++ b/src/http/schemas/transformations.ts @@ -2,6 +2,6 @@ export const transformationOptionsSchema = { height: { type: 'integer', examples: [100], minimum: 0 }, width: { type: 'integer', examples: [100], minimum: 0 }, resize: { type: 'string', enum: ['cover', 'contain', 'fill'] }, - format: { type: 'string', enum: ['origin', 'avif'] }, + format: { type: 'string', enum: ['origin', 'avif', 'webp'] }, quality: { type: 'integer', minimum: 20, maximum: 100 }, } as const diff --git a/src/internal/auth/crypto.test.ts b/src/internal/auth/crypto.test.ts new file mode 100644 index 000000000..6471b9b64 --- /dev/null +++ b/src/internal/auth/crypto.test.ts @@ -0,0 +1,46 @@ +const originalAuthEncryptionKey = process.env.AUTH_ENCRYPTION_KEY +const originalEncryptionKey = process.env.ENCRYPTION_KEY +const testEncryptionKey = 'pässwörd🔐' +const plaintext = 'payload-with-unicode-åß∂ƒ 🚀' +const legacyCiphertext = + 'U2FsdGVkX19JcSpAtQJU9fvPXdI8x6Z+4ypCCnuXdgd/Zs58/g+VpYtZbrJxC/IXXfEQuzgK4qamUe5rFiuxsA==' +const deterministicCiphertext = + 'U2FsdGVkX18AAQIDBAUGBygEQu5lvcWZgoqOtz6uMHKNaYgKr4hzXYxDM0EVHrks1kCp7vbFjcIAbNivFk4DzQ==' + +process.env.AUTH_ENCRYPTION_KEY = testEncryptionKey +process.env.ENCRYPTION_KEY = testEncryptionKey + +let encrypt: typeof import('./crypto').encrypt +let decrypt: typeof import('./crypto').decrypt + +beforeAll(async () => { + ;({ encrypt, decrypt } = await import('./crypto')) +}) + +afterAll(() => { + if (originalAuthEncryptionKey === undefined) { + delete process.env.AUTH_ENCRYPTION_KEY + } else { + process.env.AUTH_ENCRYPTION_KEY = originalAuthEncryptionKey + } + + if (originalEncryptionKey === undefined) { + delete process.env.ENCRYPTION_KEY + } else { + process.env.ENCRYPTION_KEY = originalEncryptionKey + } +}) + +describe('auth crypto', () => { + test('decrypts legacy CryptoJS ciphertext', () => { + expect(decrypt(legacyCiphertext)).toBe(plaintext) + }) + + test('decrypts fixed-salt CryptoJS ciphertext', () => { + expect(decrypt(deterministicCiphertext)).toBe(plaintext) + }) + + test('Node encrypt/decrypt roundtrip is stable', () => { + expect(decrypt(encrypt(plaintext))).toBe(plaintext) + }) +}) diff --git a/src/internal/auth/crypto.ts b/src/internal/auth/crypto.ts index ca826b65a..678fb2c21 100644 --- a/src/internal/auth/crypto.ts +++ b/src/internal/auth/crypto.ts @@ -1,21 +1,54 @@ -import AES from 'crypto-js/aes' -import Utf8 from 'crypto-js/enc-utf8' +import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'crypto' import { getConfig } from '../../config' const { encryptionKey } = getConfig() /** - * Decrypts a text with the configured encryption key via ENCRYPTION_KEY env - * @param ciphertext - */ + * Generate CryptoJs.AES key from passphrase + * https://github.com/brix/crypto-js/issues/468 + * */ +function convertPassphraseToAesKeyBuffer(key: string, salt: Buffer): Buffer { + const password = Buffer.concat([Buffer.from(key, 'utf8'), salt]) + const hash: Buffer[] = [] + let digest = password + for (let i = 0; i < 3; i++) { + hash[i] = createHash('md5').update(digest).digest() + digest = Buffer.concat([hash[i]!, password]) + } + return Buffer.concat(hash) +} + +/** + * Replicate CryptoJs.AES.decrypt method + * */ export function decrypt(ciphertext: string): string { - return AES.decrypt(ciphertext, encryptionKey).toString(Utf8) + try { + const cipherBuffer = Buffer.from(ciphertext, 'base64') + const salt = cipherBuffer.subarray(8, 16) + const keyDerivation = convertPassphraseToAesKeyBuffer(encryptionKey, salt) + const [key, iv] = [keyDerivation.subarray(0, 32), keyDerivation.subarray(32)] + const contents = cipherBuffer.subarray(16) + const decipher = createDecipheriv('aes-256-cbc', key, iv) + const decrypted = Buffer.concat([decipher.update(contents), decipher.final()]) + return decrypted.toString('utf8') + } catch (e) { + throw e + } } /** - * Encrypts a text with the configured encryption key via ENCRYPTION_KEY env - * @param plaintext - */ + * Replicate CryptoJs.AES.encrypt method + * */ export function encrypt(plaintext: string): string { - return AES.encrypt(plaintext, encryptionKey).toString() + try { + const salt = randomBytes(8) + const keyDerivation = convertPassphraseToAesKeyBuffer(encryptionKey, salt) + const [key, iv] = [keyDerivation.subarray(0, 32), keyDerivation.subarray(32)] + const cipher = createCipheriv('aes-256-cbc', key, iv) + const contents = Buffer.concat([cipher.update(plaintext), cipher.final()]) + const encrypted = Buffer.concat([Buffer.from('Salted__', 'utf8'), salt, contents]) + return encrypted.toString('base64') + } catch (e) { + throw e + } } diff --git a/src/internal/auth/jwks/channels.ts b/src/internal/auth/jwks/channels.ts new file mode 100644 index 000000000..57c12e255 --- /dev/null +++ b/src/internal/auth/jwks/channels.ts @@ -0,0 +1 @@ +export const TENANTS_JWKS_UPDATE_CHANNEL = 'tenants_jwks_update' diff --git a/src/internal/auth/jwks/generator.ts b/src/internal/auth/jwks/generator.ts index 0a4971943..81974c1e8 100644 --- a/src/internal/auth/jwks/generator.ts +++ b/src/internal/auth/jwks/generator.ts @@ -1,7 +1,7 @@ -import { logger, logSchema } from '../../monitoring' import { JwksCreateSigningSecret } from '@storage/events' -import { jwksManager } from '../../database/tenant' import { getConfig } from '../../../config' +import { jwksManager } from '../../database/tenant' +import { logger, logSchema } from '../../monitoring' const { isMultitenant, pgQueueEnable } = getConfig() diff --git a/src/internal/auth/jwks/manager.ts b/src/internal/auth/jwks/manager.ts index 4196c09bd..32bb30526 100644 --- a/src/internal/auth/jwks/manager.ts +++ b/src/internal/auth/jwks/manager.ts @@ -1,16 +1,38 @@ +import { decrypt, encrypt, generateHS512JWK } from '@internal/auth' +import { + createLruCache, + DEFAULT_CACHE_PURGE_STALE_INTERVAL_MS, + TENANT_JWKS_CACHE_NAME, +} from '@internal/cache' import { createMutexByKey } from '@internal/concurrency' -import { JwksConfig, JwksConfigKeyOCT } from '../../../config' -import { JWKSManagerStore } from './store' import { PubSubAdapter } from '@internal/pubsub' -import { decrypt, encrypt, generateHS512JWK } from '@internal/auth' import { Knex } from 'knex' +import objectSizeOf from 'object-sizeof' +import { JwksConfig, JwksConfigKeyOCT } from '../../../config' +import { TENANTS_JWKS_UPDATE_CHANNEL } from './channels' +import { JWKSManagerStore } from './store' -const TENANTS_JWKS_UPDATE_CHANNEL = 'tenants_jwks_update' const JWK_KIND_STORAGE_URL_SIGNING = 'storage-url-signing-key' const JWK_KID_SEPARATOR = '_' const tenantJwksMutex = createMutexByKey() -const tenantJwksConfigCache = new Map() +export const TENANT_JWKS_CACHE_MAX_ITEMS = 16384 +export const TENANT_JWKS_CACHE_MAX_SIZE_BYTES = 1024 * 1024 * 50 // 50 MiB +export const TENANT_JWKS_CACHE_TTL_MS = 1000 * 60 * 60 // 1h + +const tenantJwksConfigCache = createLruCache(TENANT_JWKS_CACHE_NAME, { + max: TENANT_JWKS_CACHE_MAX_ITEMS, + maxSize: TENANT_JWKS_CACHE_MAX_SIZE_BYTES, + ttl: TENANT_JWKS_CACHE_TTL_MS, + sizeCalculation: (value) => objectSizeOf(value), + updateAgeOnGet: true, + allowStale: false, + purgeStaleIntervalMs: DEFAULT_CACHE_PURGE_STALE_INTERVAL_MS, +}) + +export function deleteTenantJwksConfig(tenantId: string): void { + tenantJwksConfigCache.delete(tenantId) +} function createJwkKid({ kind, id }: { id: string; kind: string }): string { return kind + JWK_KID_SEPARATOR + id @@ -44,6 +66,30 @@ export class JWKSManager { return { kid: createJwkKid({ kind: JWK_KIND_STORAGE_URL_SIGNING, id }) } } + /** + * Atomically rolls the URL signing JWK by deactivating the current key and creating a new one + * @param tenantId + */ + async rollUrlSigningJwk(tenantId: string): Promise<{ oldKid: string | null; newKid: string }> { + return this.storage.transaction(async (trx) => { + const currentKeys = await this.storage.listActive(tenantId, JWK_KIND_STORAGE_URL_SIGNING, trx) + const currentKey = currentKeys[0] + + if (currentKey) { + await this.storage.toggleActive(tenantId, currentKey.id, false, trx) + } + + const { kid: newKid } = await this.generateUrlSigningJwk(tenantId, trx) + + return { + oldKid: currentKey + ? createJwkKid({ kind: JWK_KIND_STORAGE_URL_SIGNING, id: currentKey.id }) + : null, + newKid, + } + }) + } + /** * Adds a new jwk that can be used for signing urls * @param tenantId @@ -72,14 +118,14 @@ export class JWKSManager { async getJwksTenantConfig(tenantId: string): Promise { const cachedJwks = tenantJwksConfigCache.get(tenantId) - if (cachedJwks) { + if (cachedJwks !== undefined) { return cachedJwks } return tenantJwksMutex(tenantId, async () => { - const cachedJwks = tenantJwksConfigCache.get(tenantId) + const cachedJwks = tenantJwksConfigCache.get(tenantId, { recordMetrics: false }) - if (cachedJwks) { + if (cachedJwks !== undefined) { return cachedJwks } diff --git a/src/internal/auth/jwks/store-knex.ts b/src/internal/auth/jwks/store-knex.ts index 121875544..9c77d8ca6 100644 --- a/src/internal/auth/jwks/store-knex.ts +++ b/src/internal/auth/jwks/store-knex.ts @@ -1,9 +1,16 @@ import { Knex } from 'knex' -import { JWKStoreItem, JWKSManagerStore, PaginatedTenantItem } from './store' +import { getConfig } from '../../../config' +import { JWKSManagerStore, JWKStoreItem, PaginatedTenantItem } from './store' + +const { multitenantDatabaseQueryTimeout } = getConfig() export class JWKSManagerStoreKnex implements JWKSManagerStore { constructor(private knex: Knex) {} + async transaction(callback: (trx: Knex.Transaction) => Promise): Promise { + return this.knex.transaction(callback) + } + async insert( tenant_id: string, content: string, @@ -46,22 +53,36 @@ export class JWKSManagerStoreKnex implements JWKSManagerStore } } - async toggleActive(tenantId: string, id: string, newState: boolean): Promise { - const updated = await this.knex + async toggleActive( + tenantId: string, + id: string, + newState: boolean, + trx?: Knex.Transaction + ): Promise { + const db = trx || this.knex + const updated = await db .table('tenants_jwks') .where('id', id) .where('tenant_id', tenantId) .where('active', !newState) .update({ active: newState }) + .abortOnSignal(AbortSignal.timeout(multitenantDatabaseQueryTimeout)) return updated > 0 } - listActive(tenantId: string): Promise { - return this.knex + listActive(tenantId: string, kind?: string, trx?: Knex.Transaction): Promise { + const db = trx || this.knex + const query = db .table('tenants_jwks') .select('id', 'kind', 'content') .where('tenant_id', tenantId) .where('active', true) + + if (kind) { + query.where('kind', kind) + } + + return query.abortOnSignal(AbortSignal.timeout(multitenantDatabaseQueryTimeout)) } async listTenantsWithoutKindPaginated( @@ -81,5 +102,6 @@ export class JWKSManagerStoreKnex implements JWKSManagerStore }) .orderBy('cursor_id', 'asc') .limit(batchSize) + .abortOnSignal(AbortSignal.timeout(multitenantDatabaseQueryTimeout)) } } diff --git a/src/internal/auth/jwks/store.ts b/src/internal/auth/jwks/store.ts index a0a7dc649..05576350c 100644 --- a/src/internal/auth/jwks/store.ts +++ b/src/internal/auth/jwks/store.ts @@ -10,6 +10,12 @@ export interface PaginatedTenantItem { } export interface JWKSManagerStore { + /** + * Run operations in a transaction + * @param callback + */ + transaction(callback: (trx: TRX) => Promise): Promise + /** * Adds a jwk to the database * @param tenant_id owning tenant @@ -31,14 +37,16 @@ export interface JWKSManagerStore { * @param tenantId * @param id * @param newState + * @param trx optional transaction to use for this query */ - toggleActive(tenantId: string, id: string, newState: boolean): Promise + toggleActive(tenantId: string, id: string, newState: boolean, trx?: TRX): Promise /** * Lists all active jwks for the specified tenant * @param tenantId + * @param kind optional filter by kind */ - listActive(tenantId: string): Promise + listActive(tenantId: string, kind?: string, trx?: TRX): Promise /** * Lists tenants that do not have a jwk of the specified kind diff --git a/src/internal/auth/jwt.test.ts b/src/internal/auth/jwt.test.ts new file mode 100644 index 000000000..4ebca844b --- /dev/null +++ b/src/internal/auth/jwt.test.ts @@ -0,0 +1,395 @@ +import { JWT_CACHE_NAME } from '@internal/cache' +import { ErrorCode } from '@internal/errors' +import { cacheRequestsTotal } from '@internal/monitoring/metrics' +import * as crypto from 'crypto' +import { SignJWT } from 'jose' +import { vi } from 'vitest' +import { JwksConfigKey } from '../../config' +import { + assertValidNumericJWTExpiration, + generateHS512JWK, + getMaxNumericJWTExpiration, + signJWT, + verifyJWT, + verifyJWTWithCache, +} from './jwt' + +type TestPublicKey = { + export: () => JwksConfigKey | Record +} + +type AsymmetricKeyFixture = { + alg: 'RS256' | 'ES256' | 'EdDSA' + kid: string + publicKey: TestPublicKey + privateKey: crypto.KeyObject +} + +type HmacKeyFixture = { + alg: 'HS256' + kid?: string + publicKey: TestPublicKey + privateKey: Buffer +} + +type KeyFixture = AsymmetricKeyFixture | HmacKeyFixture +type AsymmetricKeyType = 'rsa' | 'ec' | 'ed25519' +type GeneratedKeyPair = { + publicKey: crypto.KeyObject + privateKey: crypto.KeyObject +} + +const asymmetricKeyPairFactories: Record GeneratedKeyPair> = { + rsa: () => crypto.generateKeyPairSync('rsa', { modulusLength: 2048 }), + ec: () => crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' }), + ed25519: () => crypto.generateKeyPairSync('ed25519'), +} + +function createAsymmetricKeyFixture( + type: AsymmetricKeyType, + alg: AsymmetricKeyFixture['alg'], + kid: string +): AsymmetricKeyFixture { + const { publicKey, privateKey } = asymmetricKeyPairFactories[type]() + + return { + alg, + kid, + publicKey: { + export: () => publicKey.export({ format: 'jwk' }) as JwksConfigKey, + }, + privateKey, + } +} + +describe('JWT', () => { + describe('verifyJWT with JWKS', () => { + afterEach(() => { + vi.restoreAllMocks() + vi.useRealTimers() + }) + + const keys: KeyFixture[] = [ + createAsymmetricKeyFixture('rsa', 'RS256', '0'), + createAsymmetricKeyFixture('ec', 'ES256', '1'), + createAsymmetricKeyFixture('ed25519', 'EdDSA', '2'), + ] + + const hmacPrivateKeyWithoutKid = crypto.randomBytes(256 / 8).toString('hex') + + // without kid, so the value from the secret argument will be taken + keys.push({ + alg: 'HS256', + privateKey: Buffer.from(hmacPrivateKeyWithoutKid, 'utf-8'), + publicKey: { + export: () => ({ + doesntmatter: 'wontbeused', + }), + }, + }) + + const hmacPrivateKeyWithKid = crypto.randomBytes(256 / 8).toString('hex') + + // with kid, so the value from the JWKS will be used + keys.push({ + alg: 'HS256', + kid: keys.length.toString(), + privateKey: Buffer.from(hmacPrivateKeyWithKid, 'utf-8'), + publicKey: { + export: () => ({ + kty: 'oct', + k: Buffer.from(hmacPrivateKeyWithKid, 'utf-8').toString('base64url'), + }), + }, + }) + + const jwks = { + keys: keys.map( + ({ publicKey, kid, alg }) => + ({ + ...publicKey.export(), + kid, + alg, + }) as JwksConfigKey + ), + } + + keys.forEach(({ privateKey, alg, kid }, keyIdx) => { + const iat = Math.trunc(Date.now() / 1000) + const exp = iat + 60 + + const parts = [ + Buffer.from(JSON.stringify({ typ: 'JWT', kid, alg }), 'utf-8').toString('base64url'), + Buffer.from(JSON.stringify({ sub: 'abcdef' + keyIdx, iat, exp }), 'utf-8').toString( + 'base64url' + ), + ] + + switch (alg) { + case 'EdDSA': { + // Ed25519 signs the raw message directly + const message = Buffer.from(parts.join('.')) + parts.push(crypto.sign(null, message, privateKey).toString('base64url')) + break + } + case 'ES256': { + const sign = crypto.createSign('SHA256') + sign.write(parts.join('.')) + sign.end() + parts.push( + sign.sign(Object.assign(privateKey, { dsaEncoding: 'ieee-p1363' }), 'base64url') + ) + break + } + case 'RS256': { + const sign = crypto.createSign('SHA256') + sign.write(parts.join('.')) + sign.end() + parts.push(sign.sign(privateKey, 'base64url')) + break + } + case 'HS256': { + const hmac = crypto.createHmac('SHA256', privateKey) + hmac.update(parts.join('.')) + parts.push(hmac.digest('base64url')) + break + } + } + + const jwtStr = parts.join('.') + + test(`it should verify a JWT with alg=${alg}`, async () => { + const result = await verifyJWT(jwtStr, hmacPrivateKeyWithoutKid, jwks) + expect(result.sub).toEqual('abcdef' + keyIdx) + }) + }) + + test('it should try secret if no matching jwk kty/alg found in jwks', async () => { + const jwk = await generateHS512JWK() + jwk.kid = 'abc123' + const sub = 'weird-case-secret' + const secret = crypto.randomBytes(32).toString('base64url') + + const jwtStr = await new SignJWT({ sub }) + .setIssuedAt() + .setProtectedHeader({ alg: 'HS256', kid: 'def456' }) + .sign(new TextEncoder().encode(secret)) + + const result = await verifyJWT(jwtStr, secret, { keys: [jwk] }) + expect(result.sub).toEqual(sub) + }) + + test('it should use jwt secret if jwks are missing', async () => { + const jwt = await signJWT({ sub: 'things' }, hmacPrivateKeyWithoutKid, 100) + const result = await verifyJWT(jwt, hmacPrivateKeyWithoutKid) + expect(result.sub).toEqual('things') + }) + + test('it should sign and verify using our HS256 generation', async () => { + const token = await generateHS512JWK() + token.kid = 'this-is-my-kid' + const jwt = await signJWT({ sub: 'stuff' }, token, 100) + const result = await verifyJWT(jwt, 'totally-invalid-secret-not-used', { keys: [token] }) + expect(result.sub).toEqual('stuff') + }) + + test('it should reject if secret is invalid when signing', async () => { + await expect(signJWT({ sub: 'things' }, '', 100)).rejects.toThrow( + 'Zero-length key is not supported' + ) + }) + + test('it should allow the current maximum numeric expiration and keep exp millisecond-safe', async () => { + vi.useFakeTimers() + vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z')) + + const maxNumericExpiration = getMaxNumericJWTExpiration() + const jwt = await signJWT({ sub: 'things' }, hmacPrivateKeyWithoutKid, maxNumericExpiration) + const result = await verifyJWT(jwt, hmacPrivateKeyWithoutKid) + + expect(maxNumericExpiration).toBeGreaterThan(0) + expect(Number.isSafeInteger(result.exp)).toBe(true) + expect(Number.isSafeInteger(result.exp! * 1000)).toBe(true) + }) + + test('it should reject numeric expirations above the current maximum', async () => { + vi.useFakeTimers() + vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z')) + + const maxNumericExpiration = getMaxNumericJWTExpiration() + await expect( + signJWT({ sub: 'things' }, hmacPrivateKeyWithoutKid, maxNumericExpiration + 1) + ).rejects.toMatchObject({ + code: ErrorCode.InvalidParameter, + httpStatusCode: 400, + message: 'Invalid Parameter expiresIn', + }) + }) + + test('it should reject numeric expirations above the current maximum in the shared validator', () => { + vi.useFakeTimers() + vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z')) + + expect(() => assertValidNumericJWTExpiration(getMaxNumericJWTExpiration() + 1)).toThrow( + 'Invalid Parameter expiresIn' + ) + }) + + test('it should reject numeric expirations below one second', async () => { + await expect(signJWT({ sub: 'things' }, hmacPrivateKeyWithoutKid, 0)).rejects.toMatchObject({ + code: ErrorCode.InvalidParameter, + httpStatusCode: 400, + message: 'Invalid Parameter expiresIn', + }) + + await expect(signJWT({ sub: 'things' }, hmacPrivateKeyWithoutKid, -1)).rejects.toMatchObject({ + code: ErrorCode.InvalidParameter, + httpStatusCode: 400, + message: 'Invalid Parameter expiresIn', + }) + }) + + test('it should reject numeric expirations below one second in the shared validator', () => { + expect(() => assertValidNumericJWTExpiration(0)).toThrow('Invalid Parameter expiresIn') + expect(() => assertValidNumericJWTExpiration(-1)).toThrow('Invalid Parameter expiresIn') + }) + + test('it should reject if jwt is malformed', async () => { + await expect(verifyJWT('this is not a jwt', 'and this is not a secret')).rejects.toThrow( + 'Invalid Compact JWS' + ) + }) + + test('it should reuse cached JWT verifications for the same inputs until the token expires', async () => { + vi.useFakeTimers() + vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z')) + + const addSpy = vi.spyOn(cacheRequestsTotal, 'add') + const secret = crypto.randomBytes(32).toString('base64url') + const token = await signJWT({ sub: 'cached-user' }, secret, 2) + + addSpy.mockClear() + + await expect(verifyJWTWithCache(token, secret)).resolves.toMatchObject({ + sub: 'cached-user', + }) + await expect(verifyJWTWithCache(token, secret)).resolves.toMatchObject({ + sub: 'cached-user', + }) + + expect(addSpy.mock.calls).toEqual([ + [1, { cache: JWT_CACHE_NAME, outcome: 'miss' }], + [1, { cache: JWT_CACHE_NAME, outcome: 'hit' }], + ]) + + vi.advanceTimersByTime(2200) + + await expect(verifyJWTWithCache(token, secret)).rejects.toThrow() + }) + + test('it should not reuse cached JWT verifications when the secret changes', async () => { + const addSpy = vi.spyOn(cacheRequestsTotal, 'add') + const secret = crypto.randomBytes(32).toString('base64url') + const token = await signJWT({ sub: 'cached-user' }, secret, 2) + + addSpy.mockClear() + + await expect(verifyJWTWithCache(token, secret)).resolves.toMatchObject({ + sub: 'cached-user', + }) + await expect(verifyJWTWithCache(token, 'definitely-the-wrong-secret')).rejects.toThrow() + + expect(addSpy.mock.calls).toEqual([ + [1, { cache: JWT_CACHE_NAME, outcome: 'miss' }], + [1, { cache: JWT_CACHE_NAME, outcome: 'miss' }], + ]) + }) + + test('it should not reuse cached JWT verifications when the JWKS changes', async () => { + const signingKey = await generateHS512JWK() + signingKey.kid = 'cache-signing-key' + const wrongKey = await generateHS512JWK() + wrongKey.kid = 'wrong-cache-signing-key' + const token = await signJWT({ sub: 'cached-user' }, signingKey, 2) + + await expect( + verifyJWTWithCache(token, 'invalid-secret', { keys: [signingKey] }) + ).resolves.toMatchObject({ + sub: 'cached-user', + }) + await expect( + verifyJWTWithCache(token, 'invalid-secret', { keys: [wrongKey] }) + ).rejects.toThrow() + }) + + test('it should skip caching when the token expires before the cache ttl is computed', async () => { + vi.useFakeTimers() + + const issuedAt = new Date('2026-01-01T00:00:00.000Z') + const issuedAtMs = issuedAt.getTime() + const tokenExp = issuedAtMs / 1000 + 2 + const secret = 'ttl-edge-secret' + const token = 'header.payload.signature' + + vi.setSystemTime(issuedAt) + vi.resetModules() + + const actualJose = await vi.importActual('jose') + const jwtVerifyMock = vi + .fn() + .mockImplementationOnce(async () => { + vi.setSystemTime(issuedAtMs + 2000) + return { + payload: { + sub: 'cached-user', + exp: tokenExp, + }, + } + }) + .mockResolvedValue({ + payload: { + sub: 'cached-user', + exp: tokenExp, + }, + }) + + vi.doMock('jose', () => ({ + ...actualJose, + jwtVerify: jwtVerifyMock, + })) + + try { + const { cacheRequestsTotal: isolatedCacheRequestsTotal } = await import( + '@internal/monitoring/metrics' + ) + const { verifyJWTWithCache: isolatedVerifyJWTWithCache } = await import('./jwt') + const addSpy = vi.spyOn(isolatedCacheRequestsTotal, 'add') + + addSpy.mockClear() + + await expect(isolatedVerifyJWTWithCache(token, secret)).resolves.toMatchObject({ + sub: 'cached-user', + }) + + vi.setSystemTime(issuedAtMs + 1000) + + await expect(isolatedVerifyJWTWithCache(token, secret)).resolves.toMatchObject({ + sub: 'cached-user', + }) + await expect(isolatedVerifyJWTWithCache(token, secret)).resolves.toMatchObject({ + sub: 'cached-user', + }) + + expect(jwtVerifyMock).toHaveBeenCalledTimes(2) + expect(addSpy.mock.calls).toEqual([ + [1, { cache: JWT_CACHE_NAME, outcome: 'miss' }], + [1, { cache: JWT_CACHE_NAME, outcome: 'miss' }], + [1, { cache: JWT_CACHE_NAME, outcome: 'hit' }], + ]) + } finally { + vi.doUnmock('jose') + vi.resetModules() + } + }) + }) +}) diff --git a/src/internal/auth/jwt.ts b/src/internal/auth/jwt.ts index 2c72cef40..67d74a75e 100644 --- a/src/internal/auth/jwt.ts +++ b/src/internal/auth/jwt.ts @@ -1,17 +1,22 @@ +import { createHash } from 'node:crypto' +import { + createLruCache, + DEFAULT_CACHE_PURGE_STALE_INTERVAL_MS, + JWT_CACHE_NAME, +} from '@internal/cache' import { ERRORS } from '@internal/errors' -import { getConfig, JwksConfig, JwksConfigKey, JwksConfigKeyOCT } from '../../config' import { exportJWK, generateSecret, importJWK, JWTHeaderParameters, JWTPayload, - jwtVerify, JWTVerifyGetKey, + jwtVerify, SignJWT, } from 'jose' -import { LRUCache } from 'lru-cache' import objectSizeOf from 'object-sizeof' +import { getConfig, JwksConfig, JwksConfigKey, JwksConfigKeyOCT } from '../../config' const { jwtAlgorithm } = getConfig() @@ -19,6 +24,7 @@ const JWT_HMAC_ALGOS = ['HS256', 'HS384', 'HS512'] const JWT_RSA_ALGOS = ['RS256', 'RS384', 'RS512'] const JWT_ECC_ALGOS = ['ES256', 'ES384', 'ES512'] const JWT_ED_ALGOS = ['EdDSA'] +const MAX_ABSOLUTE_JWT_EXPIRATION_SECONDS = Math.floor(Number.MAX_SAFE_INTEGER / 1000) export type SignedToken = { url: string @@ -33,6 +39,8 @@ export type SignedUploadToken = { exp: number } +const jwtJwksFingerprintCache = new WeakMap() + async function findJWKFromHeader( header: JWTHeaderParameters, secret: string, @@ -113,12 +121,46 @@ function getJWTAlgorithms(jwks: JwksConfig | null) { return algorithms } -const jwtCache = new LRUCache({ - maxSize: 1024 * 1024 * 50, // 50MB - sizeCalculation: (value) => { - return objectSizeOf(value) - }, - ttlResolution: 5000, // 5 seconds +function getJWTJwksFingerprint(jwks?: { keys: JwksConfigKey[] } | null): string { + if (!jwks) { + return 'null' + } + + const cachedFingerprint = jwtJwksFingerprintCache.get(jwks) + if (cachedFingerprint) { + return cachedFingerprint + } + + const fingerprint = createHash('sha256') + .update(JSON.stringify(jwks.keys ?? null)) + .digest('base64url') + jwtJwksFingerprintCache.set(jwks, fingerprint) + return fingerprint +} + +function getJWTCacheKey(token: string, secret: string, jwks?: { keys: JwksConfigKey[] } | null) { + const hash = createHash('sha256') + .update(token) + .update('\0') + .update(secret) + .update('\0') + .update(getJWTJwksFingerprint(jwks)) + + return hash.digest('base64url') +} + +// JWT payloads are comparatively small and high-churn, so keep a higher +// cardinality guardrail than the longer-lived config-style caches. +export const JWT_CACHE_MAX_ITEMS = 65536 +export const JWT_CACHE_MAX_SIZE_BYTES = 1024 * 1024 * 50 // 50 MiB +export const JWT_CACHE_TTL_RESOLUTION_MS = 5000 // 5 seconds + +const jwtCache = createLruCache(JWT_CACHE_NAME, { + max: JWT_CACHE_MAX_ITEMS, + maxSize: JWT_CACHE_MAX_SIZE_BYTES, + sizeCalculation: (value) => objectSizeOf(value), + ttlResolution: JWT_CACHE_TTL_RESOLUTION_MS, + purgeStaleIntervalMs: DEFAULT_CACHE_PURGE_STALE_INTERVAL_MS, }) /** @@ -133,13 +175,10 @@ export async function verifyJWTWithCache( secret: string, jwks?: { keys: JwksConfigKey[] } | null ) { - const cachedVerification = jwtCache.get(token) - if ( - cachedVerification && - cachedVerification.payload.exp && - cachedVerification.payload.exp * 1000 > Date.now() - ) { - return Promise.resolve(cachedVerification.payload) + const cacheKey = getJWTCacheKey(token, secret, jwks) + const cachedPayload = jwtCache.get(cacheKey) + if (cachedPayload && cachedPayload.exp && cachedPayload.exp * 1000 > Date.now()) { + return Promise.resolve(cachedPayload) } try { @@ -148,13 +187,10 @@ export async function verifyJWTWithCache( return payload } - jwtCache.set( - token, - { token, payload: payload }, - { - ttl: payload.exp * 1000 - Date.now(), - } - ) + const ttl = payload.exp * 1000 - Date.now() + if (ttl > 0) { + jwtCache.set(cacheKey, payload, { ttl }) + } return payload } catch (e) { throw e @@ -195,9 +231,13 @@ export async function signJWT( expiresIn: string | number | undefined ): Promise { const signer = new SignJWT(payload).setIssuedAt() - if (expiresIn) { - const expiresInStr = typeof expiresIn === 'string' ? expiresIn : Math.floor(expiresIn) + 's' - signer.setExpirationTime(expiresInStr) + if (expiresIn !== undefined) { + const expiresInStr = getJWTExpirationTime(expiresIn) + try { + signer.setExpirationTime(expiresInStr) + } catch (e) { + throw ERRORS.InvalidParameter('expiresIn', { error: e as Error }) + } } if (typeof secret === 'string') { @@ -211,6 +251,37 @@ export async function signJWT( } } +function getJWTExpirationTime(expiresIn: string | number) { + if (typeof expiresIn === 'string') { + return expiresIn + } + + assertValidNumericJWTExpiration(expiresIn) + return `${Math.floor(expiresIn)}s` +} + +export function getMaxNumericJWTExpiration(nowMs = Date.now()) { + const nowSeconds = Math.floor(nowMs / 1000) + return Math.max(0, MAX_ABSOLUTE_JWT_EXPIRATION_SECONDS - nowSeconds) +} + +export function assertValidNumericJWTExpiration(expiresIn: number, nowMs = Date.now()) { + if (!Number.isFinite(expiresIn)) { + throw ERRORS.InvalidParameter('expiresIn') + } + + const expiresInSeconds = Math.floor(expiresIn) + const maxRelativeExpirationSeconds = getMaxNumericJWTExpiration(nowMs) + + if ( + !Number.isSafeInteger(expiresInSeconds) || + expiresInSeconds < 1 || + expiresInSeconds > maxRelativeExpirationSeconds + ) { + throw ERRORS.InvalidParameter('expiresIn') + } +} + /** * Generate a new random HS512 JWK that can be used for signing JWTs */ @@ -218,3 +289,10 @@ export async function generateHS512JWK(): Promise { const secret = await generateSecret('HS512', { extractable: true }) return (await exportJWK(secret)) as JwksConfigKeyOCT } + +const JWT_SHAPE = + /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)?$/ + +export function isJwtToken(token: string) { + return token.replace('Bearer ', '').match(JWT_SHAPE) +} diff --git a/src/internal/cache/adapter.ts b/src/internal/cache/adapter.ts new file mode 100644 index 000000000..860994d3c --- /dev/null +++ b/src/internal/cache/adapter.ts @@ -0,0 +1,38 @@ +export type CacheLookupOptions = { + recordMetrics?: boolean +} + +export type CacheLookupOutcome = 'hit' | 'miss' | 'stale' + +export type CacheLookupResult = { + value: V | undefined + outcome: CacheLookupOutcome +} + +export type CacheStats = { + entries: number + sizeBytes: number +} + +export interface Cache { + get(key: K, options?: CacheLookupOptions): V | undefined + set(key: K, value: V, options?: SetOptions): void + delete(key: K): boolean +} + +export interface InspectableCache extends Cache { + getStats(): CacheStats +} + +export interface OutcomeAwareCache + extends InspectableCache { + getWithOutcome(key: K): CacheLookupResult +} + +export interface Disposable { + dispose(): void +} + +export interface DisposableCache + extends OutcomeAwareCache, + Disposable {} diff --git a/src/internal/cache/index.ts b/src/internal/cache/index.ts index eac4b280b..73edba21b 100644 --- a/src/internal/cache/index.ts +++ b/src/internal/cache/index.ts @@ -1,2 +1,4 @@ -export { objectMetadataCache } from './object-metadata-cache' -export type { CachedObjectMetadata, ObjectCacheStats } from './object-metadata-cache' +export * from './adapter' +export * from './lru' +export * from './names' +export * from './ttl' diff --git a/src/internal/cache/lru.test.ts b/src/internal/cache/lru.test.ts new file mode 100644 index 000000000..b325677c6 --- /dev/null +++ b/src/internal/cache/lru.test.ts @@ -0,0 +1,117 @@ +import { createLruCache } from '@internal/cache' +import { vi } from 'vitest' + +describe('lru cache wrapper', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.restoreAllMocks() + vi.useRealTimers() + }) + + test('reports hit miss and stale outcomes', () => { + const cache = createLruCache({ + max: 2, + ttl: 10, + allowStale: true, + perf: { + now: () => Date.now(), + }, + }) + + expect(cache.getWithOutcome('missing')).toEqual({ + value: undefined, + outcome: 'miss', + }) + + cache.set('entry', { bytes: 1 }) + + expect(cache.getWithOutcome('entry')).toEqual({ + value: { bytes: 1 }, + outcome: 'hit', + }) + + vi.advanceTimersByTime(11) + + expect(cache.getWithOutcome('entry')).toEqual({ + value: { bytes: 1 }, + outcome: 'stale', + }) + }) + + test('purges timer-driven stale entries from raw cache stats', () => { + const cache = createLruCache({ + max: 2, + maxSize: 2, + ttl: 10, + purgeStaleIntervalMs: 20, + sizeCalculation: (value) => value.bytes, + perf: { + now: () => Date.now(), + }, + }) + + cache.set('stale', { bytes: 1 }) + + expect(cache.getStats()).toEqual({ entries: 1, sizeBytes: 1 }) + + vi.advanceTimersByTime(20) + + expect(cache.getStats()).toEqual({ entries: 0, sizeBytes: 0 }) + expect(cache.get('stale')).toBeUndefined() + }) + + test('tracks calculated size as entries are replaced deleted and expired', () => { + const cache = createLruCache({ + max: 2, + maxSize: 20, + ttl: 15, + sizeCalculation: (value) => value.bytes, + perf: { + now: () => Date.now(), + }, + }) + + cache.set('a', { bytes: 3 }) + cache.set('b', { bytes: 5 }) + + expect(cache.getStats()).toEqual({ entries: 2, sizeBytes: 8 }) + + cache.set('a', { bytes: 7 }) + + expect(cache.getStats()).toEqual({ entries: 2, sizeBytes: 12 }) + + cache.delete('b') + + expect(cache.getStats()).toEqual({ entries: 1, sizeBytes: 7 }) + + vi.advanceTimersByTime(16) + + expect(cache.get('a')).toBeUndefined() + expect(cache.getStats()).toEqual({ entries: 0, sizeBytes: 0 }) + }) + + test('clears the stale purge timer on dispose', () => { + const cache = createLruCache({ + max: 2, + maxSize: 2, + ttl: 10, + purgeStaleIntervalMs: 20, + sizeCalculation: (value) => value.bytes, + perf: { + now: () => Date.now(), + }, + }) + + cache.set('stale', { bytes: 1 }) + cache.dispose() + + vi.advanceTimersByTime(20) + + expect(cache.getStats()).toEqual({ entries: 1, sizeBytes: 1 }) + + cache.dispose() + }) +}) diff --git a/src/internal/cache/lru.ts b/src/internal/cache/lru.ts new file mode 100644 index 000000000..f4fec5721 --- /dev/null +++ b/src/internal/cache/lru.ts @@ -0,0 +1,100 @@ +import { LRUCache as BaseLruCache } from 'lru-cache' +import { CacheLookupOptions, CacheLookupOutcome, DisposableCache } from './adapter' +import { monitorCache, withCacheEvictionMetrics } from './monitoring' +import { CacheName } from './names' + +export type LruCacheSetOptions = BaseLruCache.SetOptions + +export type LruCacheOptions = BaseLruCache.Options & { + purgeStaleIntervalMs?: number +} + +export const DEFAULT_CACHE_PURGE_STALE_INTERVAL_MS = 1000 * 60 // 1 minute + +export class LruCache + implements DisposableCache> +{ + private readonly cache: BaseLruCache + private readonly purgeStaleTimer?: ReturnType + + constructor(options: LruCacheOptions) { + const { purgeStaleIntervalMs, ...cacheOptions } = options + + this.cache = new BaseLruCache({ + ...cacheOptions, + }) + + if (purgeStaleIntervalMs) { + this.purgeStaleTimer = setInterval(() => { + this.cache.purgeStale() + }, purgeStaleIntervalMs) + this.purgeStaleTimer.unref?.() + } + } + + get(key: K, options?: CacheLookupOptions): V | undefined { + return this.getWithOutcome(key).value + } + + getWithOutcome(key: K) { + const status: BaseLruCache.Status = {} + const value = this.cache.get(key, { status }) + const outcome = (status.get || (value === undefined ? 'miss' : 'hit')) as CacheLookupOutcome + + return { value, outcome } + } + + set(key: K, value: V, options?: LruCacheSetOptions): void { + this.cache.set(key, value, options) + } + + delete(key: K): boolean { + return this.cache.delete(key) + } + + getStats() { + return { + entries: this.cache.size, + sizeBytes: this.cache.calculatedSize, + } + } + + purgeStale(): boolean { + return this.cache.purgeStale() + } + + dispose(): void { + if (this.purgeStaleTimer) { + clearInterval(this.purgeStaleTimer) + } + } +} + +export function createLruCache( + options: LruCacheOptions +): LruCache +export function createLruCache( + name: CacheName, + options: LruCacheOptions +): DisposableCache> +export function createLruCache( + nameOrOptions: CacheName | LruCacheOptions, + maybeOptions?: LruCacheOptions +) { + if (typeof nameOrOptions !== 'string') { + return new LruCache(nameOrOptions) + } + + const cacheName = nameOrOptions + const options = maybeOptions as LruCacheOptions + const cache = new LruCache({ + ...options, + disposeAfter: withCacheEvictionMetrics(cacheName, options.disposeAfter), + }) + + return monitorCache(cacheName, cache, { + purgeStale: () => { + cache.purgeStale() + }, + }) +} diff --git a/src/internal/cache/monitoring.test.ts b/src/internal/cache/monitoring.test.ts new file mode 100644 index 000000000..904e9ebce --- /dev/null +++ b/src/internal/cache/monitoring.test.ts @@ -0,0 +1,350 @@ +import { + createLruCache, + createTtlCache, + DEFAULT_CACHE_PURGE_STALE_INTERVAL_MS, + TENANT_CONFIG_CACHE_NAME, +} from '@internal/cache' +import { + cacheEntries, + cacheEvictionsTotal, + cacheRequestsTotal, + cacheSizeBytes, + meter, + setMetricsEnabled, +} from '@internal/monitoring/metrics' +import { vi } from 'vitest' +import { monitorCache } from './monitoring' + +function busyWaitMs(ms: number) { + const end = Date.now() + ms + while (Date.now() < end) { + // Block the event loop so TTL expires before timer-driven cleanup runs. + } +} + +describe('cache telemetry helpers', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + afterEach(() => { + vi.restoreAllMocks() + vi.useRealTimers() + }) + + test('records cache hits and misses', () => { + const addSpy = vi.spyOn(cacheRequestsTotal, 'add') + const cache = createLruCache(TENANT_CONFIG_CACHE_NAME, { + max: 2, + }) + + cache.set('hit', { ok: true }) + + expect(cache.get('hit')).toEqual({ ok: true }) + expect(cache.get('miss')).toBeUndefined() + + expect(addSpy).toHaveBeenNthCalledWith(1, 1, { + cache: TENANT_CONFIG_CACHE_NAME, + outcome: 'hit', + }) + expect(addSpy).toHaveBeenNthCalledWith(2, 1, { + cache: TENANT_CONFIG_CACHE_NAME, + outcome: 'miss', + }) + }) + + test('can read without recording cache request metrics', () => { + const addSpy = vi.spyOn(cacheRequestsTotal, 'add') + const cache = createLruCache(TENANT_CONFIG_CACHE_NAME, { + max: 2, + }) + + cache.set('hit', { ok: true }) + + expect(cache.get('hit', { recordMetrics: false })).toEqual({ ok: true }) + expect(cache.get('miss', { recordMetrics: false })).toBeUndefined() + + expect(addSpy).not.toHaveBeenCalled() + }) + + test('records stale cache reads when allowStale is enabled', () => { + const addSpy = vi.spyOn(cacheRequestsTotal, 'add') + const cache = createLruCache(TENANT_CONFIG_CACHE_NAME, { + max: 2, + ttl: 10, + allowStale: true, + perf: { + now: () => Date.now(), + }, + }) + + cache.set('stale', { ok: true }) + vi.advanceTimersByTime(11) + + expect(cache.get('stale')).toEqual({ ok: true }) + expect(addSpy).toHaveBeenCalledWith(1, { + cache: TENANT_CONFIG_CACHE_NAME, + outcome: 'stale', + }) + }) + + test('records evictions', () => { + const evictionSpy = vi.spyOn(cacheEvictionsTotal, 'add') + const cache = createLruCache(TENANT_CONFIG_CACHE_NAME, { + max: 1, + }) + + cache.set('first', { ok: true }) + cache.set('second', { ok: false }) + + expect(evictionSpy.mock.calls).toContainEqual([ + 1, + { + cache: TENANT_CONFIG_CACHE_NAME, + }, + ]) + }) + + test('records ttl cache hits and misses', () => { + const addSpy = vi.spyOn(cacheRequestsTotal, 'add') + const cache = createTtlCache(TENANT_CONFIG_CACHE_NAME, { + max: 2, + ttl: 1000, + }) + + cache.set('hit', { ok: true }) + + expect(cache.get('hit')).toEqual({ ok: true }) + expect(cache.get('miss')).toBeUndefined() + + expect(addSpy).toHaveBeenNthCalledWith(1, 1, { + cache: TENANT_CONFIG_CACHE_NAME, + outcome: 'hit', + }) + expect(addSpy).toHaveBeenNthCalledWith(2, 1, { + cache: TENANT_CONFIG_CACHE_NAME, + outcome: 'miss', + }) + }) + + test('records ttl cache evictions', () => { + const evictionSpy = vi.spyOn(cacheEvictionsTotal, 'add') + const cache = createTtlCache(TENANT_CONFIG_CACHE_NAME, { + max: 1, + ttl: 1000, + }) + + cache.set('first', { ok: true }) + cache.set('second', { ok: false }) + + expect(evictionSpy).toHaveBeenCalledWith(1, { + cache: TENANT_CONFIG_CACHE_NAME, + }) + }) + + test('chains caller disposeAfter after recording evictions', () => { + const evictionSpy = vi.spyOn(cacheEvictionsTotal, 'add') + const disposeAfter = vi.fn() + const cache = createLruCache(TENANT_CONFIG_CACHE_NAME, { + max: 1, + disposeAfter, + }) + + cache.set('first', { ok: true }) + cache.set('second', { ok: false }) + + expect(evictionSpy).toHaveBeenCalledWith(1, { + cache: TENANT_CONFIG_CACHE_NAME, + }) + expect(disposeAfter).toHaveBeenCalledWith({ ok: true }, 'first', 'evict') + expect(evictionSpy.mock.invocationCallOrder[0]).toBeLessThan( + disposeAfter.mock.invocationCallOrder[0] + ) + }) + + test('purges stale entries on the background interval', async () => { + const cache = createLruCache(TENANT_CONFIG_CACHE_NAME, { + max: 2, + maxSize: 2, + ttl: DEFAULT_CACHE_PURGE_STALE_INTERVAL_MS - 1, + purgeStaleIntervalMs: DEFAULT_CACHE_PURGE_STALE_INTERVAL_MS, + sizeCalculation: () => 1, + perf: { + now: () => Date.now(), + }, + }) + + cache.set('stale', { ok: true }) + + expect(cache.getStats()).toEqual({ entries: 1, sizeBytes: 1 }) + + vi.advanceTimersByTime(DEFAULT_CACHE_PURGE_STALE_INTERVAL_MS) + + expect(cache.getStats()).toEqual({ entries: 0, sizeBytes: 0 }) + expect(cache.get('stale')).toBeUndefined() + + cache.set('fresh', { ok: false }) + + expect(cache.getStats()).toEqual({ entries: 1, sizeBytes: 1 }) + expect(cache.get('fresh')).toEqual({ ok: false }) + + vi.advanceTimersByTime(DEFAULT_CACHE_PURGE_STALE_INTERVAL_MS) + + expect(cache.getStats()).toEqual({ entries: 0, sizeBytes: 0 }) + expect(cache.get('fresh')).toBeUndefined() + }) + + test('purges stale entries before reporting occupancy metrics', () => { + const addBatchObservableCallbackSpy = vi.spyOn(meter, 'addBatchObservableCallback') + let batchObserver: ((observer: { observe: (...args: unknown[]) => void }) => void) | undefined + + addBatchObservableCallbackSpy.mockImplementation((callback) => { + batchObserver = callback as typeof batchObserver + return undefined as never + }) + + const cache = createLruCache(TENANT_CONFIG_CACHE_NAME, { + max: 2, + maxSize: 2, + ttl: 10, + sizeCalculation: () => 1, + perf: { + now: () => Date.now(), + }, + }) + + cache.set('stale', { ok: true }) + + vi.advanceTimersByTime(11) + + expect(cache.getStats()).toEqual({ entries: 1, sizeBytes: 1 }) + + const observeSpy = vi.fn() + batchObserver?.({ observe: observeSpy }) + + expect(cache.getStats()).toEqual({ entries: 0, sizeBytes: 0 }) + expect(observeSpy).toHaveBeenCalledWith(cacheEntries, 0, { + cache: TENANT_CONFIG_CACHE_NAME, + }) + expect(observeSpy).toHaveBeenCalledWith(cacheSizeBytes, 0, { + cache: TENANT_CONFIG_CACHE_NAME, + }) + }) + + test('skips stale purges when occupancy gauges are disabled', () => { + const addBatchObservableCallbackSpy = vi.spyOn(meter, 'addBatchObservableCallback') + let batchObserver: ((observer: { observe: (...args: unknown[]) => void }) => void) | undefined + + addBatchObservableCallbackSpy.mockImplementation((callback) => { + batchObserver = callback as typeof batchObserver + return undefined as never + }) + + const purgeStale = vi.fn() + const cache = { + delete: vi.fn().mockReturnValue(false), + get: vi.fn(), + getStats: vi.fn().mockReturnValue({ entries: 1, sizeBytes: 1 }), + getWithOutcome: vi.fn().mockReturnValue({ value: undefined, outcome: 'miss' }), + set: vi.fn(), + } + + monitorCache(TENANT_CONFIG_CACHE_NAME, cache, { purgeStale }) + + try { + setMetricsEnabled([ + { name: 'cache_entries', enabled: false }, + { name: 'cache_size_bytes', enabled: false }, + ]) + + const observeSpy = vi.fn() + batchObserver?.({ observe: observeSpy }) + + expect(purgeStale).not.toHaveBeenCalled() + expect(cache.getStats).not.toHaveBeenCalled() + expect(observeSpy).not.toHaveBeenCalled() + } finally { + setMetricsEnabled([ + { name: 'cache_entries', enabled: true }, + { name: 'cache_size_bytes', enabled: true }, + ]) + } + }) + + test('records stale ttl cache reads before timer cleanup', () => { + vi.useRealTimers() + + const addSpy = vi.spyOn(cacheRequestsTotal, 'add') + const cache = createTtlCache(TENANT_CONFIG_CACHE_NAME, { + max: 2, + ttl: 10, + }) + + cache.set('stale', { ok: true }) + busyWaitMs(20) + + expect(cache.get('stale')).toEqual({ ok: true }) + expect(addSpy).toHaveBeenCalledWith(1, { + cache: TENANT_CONFIG_CACHE_NAME, + outcome: 'stale', + }) + }) + + test('purges stale ttl entries before reporting occupancy metrics', () => { + vi.useRealTimers() + + const addBatchObservableCallbackSpy = vi.spyOn(meter, 'addBatchObservableCallback') + let batchObserver: ((observer: { observe: (...args: unknown[]) => void }) => void) | undefined + + addBatchObservableCallbackSpy.mockImplementation((callback) => { + batchObserver = callback as typeof batchObserver + return undefined as never + }) + + const cache = createTtlCache(TENANT_CONFIG_CACHE_NAME, { + max: 2, + ttl: 10, + sizeCalculation: () => 1, + }) + + cache.set('stale', { ok: true }) + busyWaitMs(20) + + const observeSpy = vi.fn() + batchObserver?.({ observe: observeSpy }) + + expect(cache.getStats()).toEqual({ entries: 0, sizeBytes: 0 }) + expect(observeSpy).toHaveBeenCalledWith(cacheEntries, 0, { + cache: TENANT_CONFIG_CACHE_NAME, + }) + expect(observeSpy).toHaveBeenCalledWith(cacheSizeBytes, 0, { + cache: TENANT_CONFIG_CACHE_NAME, + }) + }) + + test('dispose unregisters occupancy callbacks and tears down wrapped caches', () => { + const addBatchObservableCallbackSpy = vi.spyOn(meter, 'addBatchObservableCallback') + const removeBatchObservableCallbackSpy = vi.spyOn(meter, 'removeBatchObservableCallback') + const cache = { + delete: vi.fn().mockReturnValue(false), + dispose: vi.fn(), + get: vi.fn(), + getStats: vi.fn().mockReturnValue({ entries: 1, sizeBytes: 1 }), + getWithOutcome: vi.fn().mockReturnValue({ value: undefined, outcome: 'miss' }), + set: vi.fn(), + } + + const monitoredCache = monitorCache(TENANT_CONFIG_CACHE_NAME, cache) + const [callback, observables] = addBatchObservableCallbackSpy.mock.calls.at(-1) as [ + Parameters[0], + Parameters[1], + ] + + monitoredCache.dispose() + monitoredCache.dispose() + + expect(removeBatchObservableCallbackSpy).toHaveBeenCalledTimes(1) + expect(removeBatchObservableCallbackSpy).toHaveBeenCalledWith(callback, observables) + expect(cache.dispose).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/internal/cache/monitoring.ts b/src/internal/cache/monitoring.ts new file mode 100644 index 000000000..97654a01f --- /dev/null +++ b/src/internal/cache/monitoring.ts @@ -0,0 +1,130 @@ +import { + cacheEntries, + cacheEvictionsTotal, + cacheRequestsTotal, + cacheSizeBytes, + isMetricEnabled, + meter, +} from '@internal/monitoring/metrics' +import { Attributes, BatchObservableCallback, Observable } from '@opentelemetry/api' +import { CacheLookupOptions, Disposable, DisposableCache, OutcomeAwareCache } from './adapter' +import { CacheName } from './names' + +type CacheDisposeHandler = (value: V, key: K, reason: R) => void + +type MonitorCacheOptions = { + purgeStale?: () => void +} + +const CACHE_OCCUPANCY_OBSERVABLES: Observable[] = [cacheEntries, cacheSizeBytes] + +function isDisposable(value: unknown): value is Disposable { + return Boolean( + value && + typeof value === 'object' && + 'dispose' in value && + typeof (value as { dispose?: unknown }).dispose === 'function' + ) +} + +function cacheAttrs( + cache: CacheName, + attrs?: Record +): Attributes { + return attrs ? { cache, ...attrs } : { cache } +} + +export function withCacheEvictionMetrics( + cacheName: CacheName, + dispose?: CacheDisposeHandler +): CacheDisposeHandler { + return (value, key, reason) => { + // Track capacity-pressure evictions only. + // TTL expiry/removal reasons are excluded on purpose. + if (reason === 'evict') { + cacheEvictionsTotal.add(1, cacheAttrs(cacheName)) + } + + dispose?.(value, key, reason) + } +} + +class MonitoredCache implements DisposableCache { + private disposed = false + private readonly observeOccupancy: BatchObservableCallback = (observer) => { + const cacheEntriesEnabled = isMetricEnabled('cache_entries') + const cacheSizeBytesEnabled = isMetricEnabled('cache_size_bytes') + + if (!cacheEntriesEnabled && !cacheSizeBytesEnabled) { + return + } + + this.options?.purgeStale?.() + const stats = this.cache.getStats() + const attrs = cacheAttrs(this.name) + + if (cacheEntriesEnabled) { + observer.observe(cacheEntries, stats.entries, attrs) + } + + if (cacheSizeBytesEnabled) { + observer.observe(cacheSizeBytes, stats.sizeBytes, attrs) + } + } + + constructor( + private readonly name: CacheName, + private readonly cache: OutcomeAwareCache, + private readonly options?: MonitorCacheOptions + ) { + meter.addBatchObservableCallback(this.observeOccupancy, CACHE_OCCUPANCY_OBSERVABLES) + } + + get(key: K, options?: CacheLookupOptions): V | undefined { + if (options?.recordMetrics === false) { + return this.cache.get(key, options) + } + + const { value, outcome } = this.cache.getWithOutcome(key) + cacheRequestsTotal.add(1, cacheAttrs(this.name, { outcome })) + + return value + } + + getWithOutcome(key: K) { + return this.cache.getWithOutcome(key) + } + + set(key: K, value: V, options?: SetOptions): void { + this.cache.set(key, value, options) + } + + delete(key: K): boolean { + return this.cache.delete(key) + } + + getStats() { + return this.cache.getStats() + } + + dispose(): void { + if (this.disposed) { + return + } + + this.disposed = true + meter.removeBatchObservableCallback(this.observeOccupancy, CACHE_OCCUPANCY_OBSERVABLES) + + if (isDisposable(this.cache)) { + this.cache.dispose() + } + } +} + +export function monitorCache( + cacheName: CacheName, + cache: OutcomeAwareCache, + options?: MonitorCacheOptions +): DisposableCache { + return new MonitoredCache(cacheName, cache, options) +} diff --git a/src/internal/cache/names.ts b/src/internal/cache/names.ts new file mode 100644 index 000000000..cb9c0e7d3 --- /dev/null +++ b/src/internal/cache/names.ts @@ -0,0 +1,12 @@ +export const JWT_CACHE_NAME = 'jwt' as const +export const TENANT_CONFIG_CACHE_NAME = 'tenant_config' as const +export const TENANT_JWKS_CACHE_NAME = 'tenant_jwks' as const +export const TENANT_POOL_CACHE_NAME = 'tenant_pool' as const +export const TENANT_S3_CREDENTIALS_CACHE_NAME = 'tenant_s3_credentials' as const + +export type CacheName = + | typeof JWT_CACHE_NAME + | typeof TENANT_CONFIG_CACHE_NAME + | typeof TENANT_JWKS_CACHE_NAME + | typeof TENANT_POOL_CACHE_NAME + | typeof TENANT_S3_CREDENTIALS_CACHE_NAME diff --git a/src/internal/cache/object-metadata-cache.ts b/src/internal/cache/object-metadata-cache.ts deleted file mode 100644 index ff1729cd1..000000000 --- a/src/internal/cache/object-metadata-cache.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { LRUCache } from 'lru-cache' -import { - ObjectCacheSizeGauge, - ObjectCacheHitsCounter, - ObjectCacheMissesCounter, - ObjectCacheEvictionsCounter, - ObjectCacheHitRateGauge, -} from '@internal/monitoring/metrics' -import { getConfig } from '../../config' - -const { region } = getConfig() - -/** - * ObjectMetadataCache - * - * In-memory cache for object metadata to reduce database queries during - * COG/geospatial workloads where GDAL makes hundreds of range requests - * to the same object within seconds. - * - * This eliminates the need for DB queries on every tile request, reducing - * latency from ~20-30ms to <1ms for cached objects. - * - * Key features: - * - TTL of 30 seconds (safe for high-frequency read patterns) - * - Max 10,000 entries (prevents memory bloat) - * - LRU eviction (prioritizes recently accessed objects) - * - Size-aware limits (prevents single large objects from consuming all cache) - */ - -interface CachedObjectMetadata { - id: string - version: string - bucket_id: string - metadata?: Record - user_metadata?: Record - created_at?: Date - updated_at?: Date - // Track when this was cached to help with debugging - cachedAt: number -} - -interface ObjectCacheStats { - hits: number - misses: number - evictions: number - size: number -} - -class ObjectMetadataCache { - private cache: LRUCache - private stats: ObjectCacheStats = { - hits: 0, - misses: 0, - evictions: 0, - size: 0, - } - - constructor() { - this.cache = new LRUCache({ - // Maximum number of objects to cache - max: 10000, - - // TTL: 30 seconds - long enough to handle COG tile bursts, - // short enough to not serve stale data - ttl: 30 * 1000, - - // Update TTL on access (extends cache for active objects) - updateAgeOnGet: true, - - // Size tracking to prevent memory bloat - maxSize: 100 * 1024 * 1024, // 100MB max cache size - sizeCalculation: (value) => { - // Rough size estimation - return JSON.stringify(value).length - }, - - // Track evictions for metrics - dispose: () => { - this.stats.evictions++ - ObjectCacheEvictionsCounter.inc({ region }) - }, - }) - - // Start metrics updater - this.startMetricsUpdater() - } - - /** - * Periodically update Prometheus metrics - */ - private startMetricsUpdater() { - setInterval(() => { - const stats = this.getStats() - ObjectCacheSizeGauge.set({ region }, stats.size) - ObjectCacheHitRateGauge.set({ region }, stats.hitRate) - }, 5000) // Update every 5 seconds - } - - /** - * Generate cache key from tenant, bucket, and object path - */ - private getCacheKey(tenantId: string, bucketName: string, objectName: string): string { - return `${tenantId}:${bucketName}:${objectName}` - } - - /** - * Get cached object metadata - * Returns undefined if not cached or expired - */ - get(tenantId: string, bucketName: string, objectName: string): CachedObjectMetadata | undefined { - const key = this.getCacheKey(tenantId, bucketName, objectName) - const value = this.cache.get(key) - - if (value) { - this.stats.hits++ - ObjectCacheHitsCounter.inc({ region }) - } else { - this.stats.misses++ - ObjectCacheMissesCounter.inc({ region }) - } - - return value - } - - /** - * Cache object metadata - */ - set( - tenantId: string, - bucketName: string, - objectName: string, - metadata: Omit - ): void { - const key = this.getCacheKey(tenantId, bucketName, objectName) - this.cache.set(key, { - ...metadata, - cachedAt: Date.now(), - }) - this.stats.size = this.cache.size - } - - /** - * Invalidate cache for a specific object - * Use when object is updated or deleted - */ - invalidate(tenantId: string, bucketName: string, objectName: string): void { - const key = this.getCacheKey(tenantId, bucketName, objectName) - this.cache.delete(key) - this.stats.size = this.cache.size - } - - /** - * Invalidate all objects in a bucket - * Use when bucket is updated or deleted - */ - invalidateBucket(tenantId: string, bucketName: string): void { - const prefix = `${tenantId}:${bucketName}:` - const keysToDelete: string[] = [] - - // Collect keys to delete - for (const key of this.cache.keys()) { - if (key.startsWith(prefix)) { - keysToDelete.push(key) - } - } - - // Delete them - for (const key of keysToDelete) { - this.cache.delete(key) - } - - this.stats.size = this.cache.size - } - - /** - * Clear entire cache - */ - clear(): void { - this.cache.clear() - this.stats = { - hits: 0, - misses: 0, - evictions: 0, - size: 0, - } - } - - /** - * Get cache statistics for monitoring - */ - getStats(): ObjectCacheStats & { hitRate: number } { - const total = this.stats.hits + this.stats.misses - const hitRate = total > 0 ? this.stats.hits / total : 0 - - return { - ...this.stats, - hitRate, - } - } - - /** - * Get cache info for debugging - */ - getInfo() { - return { - size: this.cache.size, - maxSize: this.cache.max, - calculatedSize: this.cache.calculatedSize, - maxCalculatedSize: this.cache.maxSize, - } - } -} - -// Singleton instance -export const objectMetadataCache = new ObjectMetadataCache() - -export type { CachedObjectMetadata, ObjectCacheStats } diff --git a/src/internal/cache/ttl.test.ts b/src/internal/cache/ttl.test.ts new file mode 100644 index 000000000..7ad50ca98 --- /dev/null +++ b/src/internal/cache/ttl.test.ts @@ -0,0 +1,151 @@ +import { vi } from 'vitest' + +describe('ttl cache wrapper', () => { + let createTtlCache: typeof import('./ttl').createTtlCache + + beforeAll(async () => { + vi.useFakeTimers() + ;({ createTtlCache } = await import('./ttl')) + }) + + beforeEach(() => { + vi.clearAllTimers() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + afterAll(() => { + vi.useRealTimers() + }) + + test('purges stale entries from stats and iteration after ttl elapses', async () => { + const cache = createTtlCache({ + max: 10, + ttl: 20, + sizeCalculation: (value) => value.bytes, + }) + + cache.set('stale', { bytes: 4 }) + + expect(cache.getStats()).toEqual({ entries: 1, sizeBytes: 4 }) + + await vi.advanceTimersByTimeAsync(40) + + expect(cache.getStats()).toEqual({ entries: 0, sizeBytes: 0 }) + expect([...cache.entries()]).toEqual([]) + expect([...cache.values()]).toEqual([]) + }) + + test('keeps replaced keys visible in iteration and size tracking', () => { + const cache = createTtlCache({ + max: 10, + ttl: Infinity, + sizeCalculation: (value) => value.bytes, + }) + + cache.set('a', { bytes: 3 }) + cache.set('a', { bytes: 7 }) + + expect(cache.getStats()).toEqual({ entries: 1, sizeBytes: 7 }) + expect([...cache.entries()]).toEqual([['a', { bytes: 7 }]]) + expect([...cache.keys()]).toEqual(['a']) + expect([...cache.values()]).toEqual([{ bytes: 7 }]) + }) + + test('tracks iteration and calculated size', () => { + const cache = createTtlCache({ + max: 10, + ttl: Infinity, + sizeCalculation: (value) => value.bytes, + }) + + cache.set('a', { bytes: 3 }) + cache.set('b', { bytes: 5 }) + + expect(cache.getStats()).toEqual({ entries: 2, sizeBytes: 8 }) + expect([...cache.entries()]).toEqual([ + ['a', { bytes: 3 }], + ['b', { bytes: 5 }], + ]) + expect([...cache.values()]).toEqual([{ bytes: 3 }, { bytes: 5 }]) + + cache.set('a', { bytes: 7 }) + + expect(cache.getStats()).toEqual({ entries: 2, sizeBytes: 12 }) + expect(cache.get('a')).toEqual({ bytes: 7 }) + + cache.delete('b') + + expect(cache.getStats()).toEqual({ entries: 1, sizeBytes: 7 }) + + cache.clear() + + expect(cache.getStats()).toEqual({ entries: 0, sizeBytes: 0 }) + expect([...cache.entries()]).toEqual([]) + + cache.set('c', { bytes: 2 }) + + expect(cache.getStats()).toEqual({ entries: 1, sizeBytes: 2 }) + expect([...cache.entries()]).toEqual([['c', { bytes: 2 }]]) + }) + + test('keeps stats and iteration in sync when max capacity evicts entries', () => { + const cache = createTtlCache({ + max: 1, + ttl: 1000, + sizeCalculation: (value) => value.bytes, + }) + + cache.set('a', { bytes: 3 }) + cache.set('b', { bytes: 5 }) + + expect(cache.getStats()).toEqual({ entries: 1, sizeBytes: 5 }) + expect([...cache.entries()]).toEqual([['b', { bytes: 5 }]]) + expect([...cache.keys()]).toEqual(['b']) + expect([...cache.values()]).toEqual([{ bytes: 5 }]) + expect(cache.get('a')).toBeUndefined() + expect(cache.get('b')).toEqual({ bytes: 5 }) + }) + + test('does not extend ttl when iterating entries keys or values', async () => { + const cache = createTtlCache({ + max: 10, + ttl: 40, + updateAgeOnGet: true, + checkAgeOnGet: true, + sizeCalculation: (value) => value.bytes, + }) + + cache.set('a', { bytes: 3 }) + + await vi.advanceTimersByTimeAsync(20) + + expect([...cache.entries()]).toEqual([['a', { bytes: 3 }]]) + expect([...cache.keys()]).toEqual(['a']) + expect([...cache.values()]).toEqual([{ bytes: 3 }]) + + await vi.advanceTimersByTimeAsync(30) + + expect(cache.get('a')).toBeUndefined() + expect(cache.getStats()).toEqual({ entries: 0, sizeBytes: 0 }) + }) + + test('cancels the ttl timer on dispose', async () => { + const cache = createTtlCache({ + max: 10, + ttl: 20, + sizeCalculation: (value) => value.bytes, + }) + + cache.set('stale', { bytes: 4 }) + cache.dispose() + + await vi.advanceTimersByTimeAsync(40) + + expect(cache.getStats()).toEqual({ entries: 1, sizeBytes: 4 }) + + cache.dispose() + }) +}) diff --git a/src/internal/cache/ttl.ts b/src/internal/cache/ttl.ts new file mode 100644 index 000000000..907bbb267 --- /dev/null +++ b/src/internal/cache/ttl.ts @@ -0,0 +1,182 @@ +import BaseTtlCache from '@isaacs/ttlcache' +import { CacheLookupOptions, CacheLookupOutcome, DisposableCache } from './adapter' +import { monitorCache, withCacheEvictionMetrics } from './monitoring' +import { CacheName } from './names' + +export type TtlCacheSetOptions = BaseTtlCache.SetOptions + +export type TtlCacheOptions = BaseTtlCache.Options & { + sizeCalculation?: (value: V, key: K) => number +} + +export class TtlCache implements DisposableCache { + private readonly cache: BaseTtlCache + private readonly sizeCalculation?: (value: V, key: K) => number + private calculatedSize = 0 + private readonly keySizes = new Map() + private readonly keysInCache = new Set() + + constructor(options: TtlCacheOptions) { + const { dispose, sizeCalculation, ...cacheOptions } = options + + this.sizeCalculation = sizeCalculation + this.cache = new BaseTtlCache({ + ...cacheOptions, + dispose: (value, key, reason) => { + this.keysInCache.delete(key) + this.deleteTrackedSize(key) + dispose?.(value, key, reason) + }, + }) + } + + get(key: K, options?: CacheLookupOptions): V | undefined { + return this.getWithOutcome(key).value + } + + getWithOutcome(key: K) { + const remainingTTL = this.cache.getRemainingTTL(key) + const value = this.cache.get(key) + const outcome: CacheLookupOutcome = + remainingTTL > 0 || remainingTTL === Infinity + ? value === undefined + ? 'miss' + : 'hit' + : value === undefined + ? 'miss' + : 'stale' + + return { value, outcome } + } + + set(key: K, value: V, options?: TtlCacheSetOptions): void { + this.cache.set(key, value, options) + this.keysInCache.add(key) + this.setTrackedSize(key, value) + } + + delete(key: K): boolean { + return this.cache.delete(key) + } + + clear(): void { + this.cache.clear() + this.keysInCache.clear() + this.keySizes.clear() + this.calculatedSize = 0 + } + + has(key: K): boolean { + return this.cache.has(key) + } + + purgeStale(): boolean { + return Boolean(this.cache.purgeStale()) + } + + cancelTimer(): void { + this.cache.cancelTimer() + } + + dispose(): void { + this.cancelTimer() + } + + *entries(): Generator<[K, V]> { + for (const key of this.keysInCache) { + const value = this.cache.get(key, { + updateAgeOnGet: false, + checkAgeOnGet: true, + }) + + if (value === undefined) { + continue + } + + yield [key, value] + } + } + + *keys(): Generator { + for (const key of this.keysInCache) { + if (this.cache.getRemainingTTL(key) === 0) { + continue + } + + yield key + } + } + + *values(): Generator { + for (const [, value] of this.entries()) { + yield value + } + } + + getRemainingTTL(key: K): number { + return this.cache.getRemainingTTL(key) + } + + setTTL(key: K, ttl?: number): void { + this.cache.setTTL(key, ttl) + } + + getStats() { + return { + entries: this.cache.size, + sizeBytes: this.calculatedSize, + } + } + + [Symbol.iterator](): Iterator<[K, V]> { + return this.entries() + } + + private setTrackedSize(key: K, value: V) { + if (!this.sizeCalculation) { + return + } + + const nextSize = this.sizeCalculation(value, key) + const previousSize = this.keySizes.get(key) ?? 0 + this.keySizes.set(key, nextSize) + this.calculatedSize += nextSize - previousSize + } + + private deleteTrackedSize(key: K) { + const previousSize = this.keySizes.get(key) + if (previousSize === undefined) { + return + } + + this.keySizes.delete(key) + this.calculatedSize -= previousSize + } +} + +export function createTtlCache(options: TtlCacheOptions): TtlCache +export function createTtlCache( + name: CacheName, + options: TtlCacheOptions +): DisposableCache +export function createTtlCache( + nameOrOptions: CacheName | TtlCacheOptions, + maybeOptions?: TtlCacheOptions +) { + if (typeof nameOrOptions !== 'string') { + return new TtlCache(nameOrOptions) + } + + const cacheName = nameOrOptions + const options = maybeOptions as TtlCacheOptions + const cache = new TtlCache({ + ...options, + dispose: withCacheEvictionMetrics(cacheName, options.dispose), + }) + + return monitorCache(cacheName, cache, { + purgeStale: () => { + cache.purgeStale() + }, + }) +} diff --git a/src/internal/cluster/cluster.ts b/src/internal/cluster/cluster.ts index 74b7e7f95..106c75726 100644 --- a/src/internal/cluster/cluster.ts +++ b/src/internal/cluster/cluster.ts @@ -1,6 +1,6 @@ -import { ClusterDiscoveryECS } from '@internal/cluster/ecs' - import { EventEmitter } from 'node:events' +import { ClusterDiscoveryECS } from '@internal/cluster/ecs' +import { ClusterDiscoveryEKS } from '@internal/cluster/eks' import { logger } from '@internal/monitoring' const clusterEvent = new EventEmitter() @@ -14,17 +14,28 @@ export class Cluster { } static async init(abortSignal: AbortSignal) { + let cluster: ClusterDiscoveryECS | ClusterDiscoveryEKS | null = null + if (process.env.CLUSTER_DISCOVERY === 'ECS') { - const cluster = new ClusterDiscoveryECS() + cluster = new ClusterDiscoveryECS() + } else if (process.env.CLUSTER_DISCOVERY === 'EKS') { + cluster = new ClusterDiscoveryEKS() + } + + if (cluster) { Cluster.size = await cluster.getClusterSize() - logger.info(`[Cluster] Initial cluster size ${Cluster.size}`, { - type: 'cluster', - clusterSize: Cluster.size, - }) + logger.info( + { + type: 'cluster', + clusterSize: Cluster.size, + discoveryType: process.env.CLUSTER_DISCOVERY, + }, + `[Cluster] Initial cluster size ${Cluster.size}` + ) Cluster.watcher = setInterval(() => { - cluster + cluster! .getClusterSize() .then((size) => { if (size && size !== Cluster.size) { diff --git a/src/internal/cluster/ecs.ts b/src/internal/cluster/ecs.ts index dce0eb4e7..c98533f77 100644 --- a/src/internal/cluster/ecs.ts +++ b/src/internal/cluster/ecs.ts @@ -1,6 +1,6 @@ import { ECSClient, ListTasksCommand } from '@aws-sdk/client-ecs' +import { DesiredStatus } from '@aws-sdk/client-ecs/dist-types/models/enums' import axios from 'axios' -import { DesiredStatus } from '@aws-sdk/client-ecs/dist-types/models/models_0' export class ClusterDiscoveryECS { private client: ECSClient diff --git a/src/internal/cluster/eks.ts b/src/internal/cluster/eks.ts new file mode 100644 index 000000000..61494b584 --- /dev/null +++ b/src/internal/cluster/eks.ts @@ -0,0 +1,67 @@ +import * as k8s from '@kubernetes/client-node' + +/** + * ClusterDiscoveryEKS provides cluster size discovery for Kubernetes/EKS environments. + * + * Environment Variables: + * - KUBERNETES_NAMESPACE: The namespace to monitor + * - KUBERNETES_LABEL_SELECTOR: Label selector for storage pods + * - KUBERNETES_SERVICE_HOST: The host of the Kubernetes service. This is present when running inside a pod and does not need to be set externally. + */ +export class ClusterDiscoveryEKS { + private client: k8s.CoreV1Api + private namespace: string + private labelSelector: string + + constructor() { + const kc = new k8s.KubeConfig() + + // Always load config from cluster when running inside a pod + if (process.env.KUBERNETES_SERVICE_HOST) { + kc.loadFromCluster() + } else { + throw new Error( + 'EKS cluster discovery is not supported when running outside of a Kubernetes cluster' + ) + } + + if (!process.env.KUBERNETES_LABEL_SELECTOR) { + throw new Error('KUBERNETES_LABEL_SELECTOR is not set') + } + + this.client = kc.makeApiClient(k8s.CoreV1Api) + + // Get namespace from environment or default to current namespace + this.namespace = process.env.KUBERNETES_NAMESPACE || 'storage' + + // Label selector to identify storage pods + this.labelSelector = process.env.KUBERNETES_LABEL_SELECTOR + } + + async getClusterSize(): Promise { + try { + return await this.listPods() + } catch (error) { + throw new Error(`Failed to get cluster size: ${error}`) + } + } + + private async listPods(): Promise { + try { + const response = await this.client.listNamespacedPod({ + namespace: this.namespace, + labelSelector: this.labelSelector, + }) + + const pods = response.items || [] + const filteredPods = pods.filter((pod: k8s.V1Pod) => { + const podPhase = pod.status?.phase + return podPhase === 'Pending' || podPhase === 'Running' + }) + + return filteredPods.length + } catch (error) { + throw new Error(`Failed to list pods: ${error}`) + } + } +} diff --git a/src/internal/concurrency/async-abort-controller.test.ts b/src/internal/concurrency/async-abort-controller.test.ts new file mode 100644 index 000000000..9e320333d --- /dev/null +++ b/src/internal/concurrency/async-abort-controller.test.ts @@ -0,0 +1,181 @@ +import { AsyncAbortController } from '@internal/concurrency' +import { vi } from 'vitest' + +describe('AsyncAbortController', () => { + it('reuses nextGroup when accessed repeatedly', () => { + const controller = new AsyncAbortController() + + const firstGroup = controller.nextGroup + const secondGroup = controller.nextGroup + + expect(secondGroup).toBe(firstGroup) + }) + + it('reuses nested nextGroup access at each level', () => { + const controller = new AsyncAbortController() + const firstGroup = controller.nextGroup + + const firstNestedGroup = controller.nextGroup.nextGroup + const secondNestedGroup = controller.nextGroup.nextGroup + + expect(controller.nextGroup).toBe(firstGroup) + expect(secondNestedGroup).toBe(firstNestedGroup) + expect(firstNestedGroup).toBe(firstGroup.nextGroup) + }) + + it('aborts a nextGroup child only once even after repeated access', async () => { + const controller = new AsyncAbortController() + const childGroup = controller.nextGroup + const abortSpy = vi.spyOn(childGroup, 'abortAsync').mockResolvedValue(undefined) + + void controller.nextGroup + void controller.nextGroup + + await controller.abortAsync() + + expect(abortSpy).toHaveBeenCalledTimes(1) + }) + + it('waits for parent abort handlers before aborting nested groups', async () => { + const controller = new AsyncAbortController() + const childGroup = controller.nextGroup + const grandchildGroup = childGroup.nextGroup + const order: string[] = [] + let releaseRootAbort!: () => void + const rootAbortDone = new Promise((resolve) => { + releaseRootAbort = resolve + }) + + controller.signal.addEventListener('abort', async () => { + order.push('root:start') + await rootAbortDone + order.push('root:end') + }) + + childGroup.signal.addEventListener('abort', () => { + order.push('child') + }) + + grandchildGroup.signal.addEventListener('abort', () => { + order.push('grandchild') + }) + + const abortPromise = controller.abortAsync() + + await Promise.resolve() // force microtask tick + expect(order).toEqual(['root:start']) + + releaseRootAbort() + await abortPromise + + expect(order).toEqual(['root:start', 'root:end', 'child', 'grandchild']) + }) + + it('forwards the real abort event to function listeners with the signal as context', async () => { + const controller = new AsyncAbortController() + const seen: { + target: EventTarget | null + currentTarget: EventTarget | null + context: unknown + } = { + target: null, + currentTarget: null, + context: undefined, + } + + controller.signal.addEventListener('abort', function (event) { + seen.target = event.target + seen.currentTarget = event.currentTarget + seen.context = this + }) + + await controller.abortAsync() + + expect(seen.target).toBe(controller.signal) + expect(seen.currentTarget).toBe(controller.signal) + expect(seen.context).toBe(controller.signal) + }) + + it('waits for handleEvent listeners before aborting nested groups', async () => { + const controller = new AsyncAbortController() + const childGroup = controller.nextGroup + const order: string[] = [] + let releaseRootAbort!: () => void + const rootAbortDone = new Promise((resolve) => { + releaseRootAbort = resolve + }) + const listener = { + target: null as EventTarget | null, + async handleEvent(event: Event) { + this.target = event.target + order.push('root:start') + await rootAbortDone + order.push('root:end') + }, + } + + controller.signal.addEventListener('abort', listener) + childGroup.signal.addEventListener('abort', () => { + order.push('child') + }) + + const abortPromise = controller.abortAsync() + + await Promise.resolve() + expect(order).toEqual(['root:start']) + + releaseRootAbort() + await abortPromise + + expect(listener.target).toBe(controller.signal) + expect(order).toEqual(['root:start', 'root:end', 'child']) + }) + + it('ignores null abort listeners', async () => { + const controller = new AsyncAbortController() + const nullListener = null as unknown as EventListenerOrEventListenerObject + + expect(() => controller.signal.addEventListener('abort', nullListener)).not.toThrow() + await expect(controller.abortAsync()).resolves.toBeUndefined() + }) + + it('does not invoke or wait on explicitly removed abort listeners', async () => { + const controller = new AsyncAbortController() + const listener = vi.fn() + + controller.signal.addEventListener('abort', listener) + controller.signal.removeEventListener('abort', listener) + + await expect(controller.abortAsync()).resolves.toBeUndefined() + expect(listener).not.toHaveBeenCalled() + }) + + it('does not invoke or wait on abort listeners removed by a registration signal', async () => { + const controller = new AsyncAbortController() + const registration = new AbortController() + const listener = vi.fn() + + controller.signal.addEventListener('abort', listener, { + signal: registration.signal, + }) + + registration.abort() + + await expect(controller.abortAsync()).resolves.toBeUndefined() + expect(listener).not.toHaveBeenCalled() + }) + + it('ignores abort listeners registered with an already aborted signal', async () => { + const controller = new AsyncAbortController() + const registration = new AbortController() + const listener = vi.fn() + + registration.abort() + controller.signal.addEventListener('abort', listener, { + signal: registration.signal, + }) + + await expect(controller.abortAsync()).resolves.toBeUndefined() + expect(listener).not.toHaveBeenCalled() + }) +}) diff --git a/src/internal/concurrency/async-abort-controller.ts b/src/internal/concurrency/async-abort-controller.ts index 65cb5238a..9a7d58a09 100644 --- a/src/internal/concurrency/async-abort-controller.ts +++ b/src/internal/concurrency/async-abort-controller.ts @@ -1,73 +1,228 @@ /** * This special AbortController is used to wait for all the abort handlers to finish before resolving the promise. */ +type AbortListener = EventListenerOrEventListenerObject + +type ListenerRecord = { + wrapped: EventListener + cleanup: () => void +} + export class AsyncAbortController extends AbortController { - protected promises: Promise[] = [] - protected priority = 0 - protected groups = new Map() + protected runningPromises = new Set>() + protected abortListeners = new WeakMap>() + protected _nextGroup?: AsyncAbortController constructor() { super() - const originalEventListener = this.signal.addEventListener + const originalAddEventListener = this.signal.addEventListener.bind(this.signal) + const originalRemoveEventListener = this.signal.removeEventListener.bind(this.signal) // Patch event addEventListener to keep track of listeners and their promises - this.signal.addEventListener = (type: string, listener: any, options: any) => { + this.signal.addEventListener = ( + type: string, + listener: EventListenerOrEventListenerObject | null, + options?: boolean | AddEventListenerOptions + ) => { + if (!listener) { + return + } + if (type !== 'abort') { - return originalEventListener.call(this.signal, type, listener, options) + return originalAddEventListener(type, listener, options) } - let resolving: undefined | (() => Promise) = undefined - const promise = new Promise(async (resolve, reject) => { - resolving = async (): Promise => { - try { - const result = await listener() - resolve(result) - } catch (e) { - reject(e) - } - } - }) - this.promises.push(promise) + if (this.signal.aborted) { + return originalAddEventListener(type, listener, options) + } - if (!resolving) { - throw new Error('resolve is undefined') + const capture = getCaptureOption(options) + const existingRecord = this.getAbortListenerRecord(listener, capture) + if (existingRecord) { + return originalAddEventListener(type, existingRecord.wrapped, options) } - return originalEventListener.call(this.signal, type, resolving, options) + const registrationSignal = getRegistrationSignal(options) + if (registrationSignal?.aborted) { + return + } + + let wrapped!: EventListener + const cleanupRegistrationSignal = this.watchListenerRemovalSignal( + registrationSignal, + listener, + capture + ) + + wrapped = (event: Event) => { + this.deleteAbortListenerRecord(listener, capture) + originalRemoveEventListener(type, wrapped, capture) + + const runningPromise = this.invokeAbortListener(listener, event) + this.runningPromises.add(runningPromise) + void runningPromise.finally(() => { + this.runningPromises.delete(runningPromise) + }) + } + + this.setAbortListenerRecord(listener, capture, { + wrapped, + cleanup: cleanupRegistrationSignal, + }) + + return originalAddEventListener(type, wrapped, options) } - } - protected _nextGroup?: AsyncAbortController + this.signal.removeEventListener = ( + type: string, + listener: EventListenerOrEventListenerObject | null, + options?: boolean | EventListenerOptions + ) => { + if (!listener) { + return + } - get nextGroup() { - if (!this._nextGroup) { - this._nextGroup = new AsyncAbortController() - this._nextGroup.priority = this.priority + 1 + if (type !== 'abort') { + return originalRemoveEventListener(type, listener, options) + } + + const capture = getCaptureOption(options) + const record = this.getAbortListenerRecord(listener, capture) + if (!record) { + return originalRemoveEventListener(type, listener, options) + } + + this.deleteAbortListenerRecord(listener, capture) + return originalRemoveEventListener(type, record.wrapped, options) } + } - let existingGroups = this.groups.get(this._nextGroup.priority) - if (!existingGroups) { - existingGroups = [] + get nextGroup() { + if (this._nextGroup) { + return this._nextGroup } - existingGroups.push(this._nextGroup) - this.groups.set(this._nextGroup.priority, existingGroups) + this._nextGroup = new AsyncAbortController() return this._nextGroup } async abortAsync() { this.abort() - while (this.promises.length > 0) { - const promises = this.promises.splice(0, 100) + while (this.runningPromises.size > 0) { + const promises = Array.from(this.runningPromises) await Promise.allSettled(promises) } - await this.abortGroups() + await this.abortNextGroup() + } + + protected async abortNextGroup() { + if (this._nextGroup) { + await this._nextGroup.abortAsync() + } + } + + protected invokeAbortListener(listener: AbortListener, event: Event): Promise { + try { + const result = + typeof listener === 'function' + ? listener.call(this.signal, event) + : listener.handleEvent(event) + + return Promise.resolve(result).then(() => undefined) + } catch (error) { + return Promise.reject(error) + } + } + + protected getAbortListenerRecord( + listener: AbortListener, + capture: boolean + ): ListenerRecord | undefined { + return this.abortListeners.get(listener)?.get(capture) + } + + protected setAbortListenerRecord( + listener: AbortListener, + capture: boolean, + record: ListenerRecord + ) { + const records = this.abortListeners.get(listener) ?? new Map() + records.set(capture, record) + this.abortListeners.set(listener, records) } - protected async abortGroups() { - for (const [, group] of this.groups) { - await Promise.allSettled(group.map((g) => g.abortAsync())) + protected deleteAbortListenerRecord(listener: AbortListener, capture: boolean) { + const records = this.abortListeners.get(listener) + const record = records?.get(capture) + if (!records || !record) { + return + } + + record.cleanup() + records.delete(capture) + + if (records.size === 0) { + this.abortListeners.delete(listener) } } + + protected watchListenerRemovalSignal( + signal: AbortSignal | undefined, + listener: AbortListener, + capture: boolean + ): () => void { + if (!signal) { + return () => {} + } + + const onAbort = () => { + this.deleteAbortListenerRecord(listener, capture) + } + + addNativeEventListener(signal, 'abort', onAbort, { once: true }) + + return () => { + removeNativeEventListener(signal, 'abort', onAbort, { capture: false }) + } + } +} + +const nativeAddEventListener = EventTarget.prototype.addEventListener +const nativeRemoveEventListener = EventTarget.prototype.removeEventListener + +function addNativeEventListener( + target: EventTarget, + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions +) { + nativeAddEventListener.call(target, type, listener, options) +} + +function removeNativeEventListener( + target: EventTarget, + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | EventListenerOptions +) { + nativeRemoveEventListener.call(target, type, listener, options) +} + +function getCaptureOption(options?: boolean | EventListenerOptions): boolean { + if (typeof options === 'boolean') { + return options + } + + return options?.capture ?? false +} + +function getRegistrationSignal( + options?: boolean | AddEventListenerOptions +): AbortSignal | undefined { + if (typeof options === 'boolean') { + return undefined + } + + return options?.signal } diff --git a/src/internal/concurrency/index.ts b/src/internal/concurrency/index.ts index 13f09efd5..3d426794f 100644 --- a/src/internal/concurrency/index.ts +++ b/src/internal/concurrency/index.ts @@ -1,4 +1,4 @@ -export * from './mutex' -export * from './wait' export * from './async-abort-controller' export * from './merge-async-itertor' +export * from './mutex' +export * from './wait' diff --git a/src/internal/database/client.ts b/src/internal/database/client.ts index 1fc53b9a0..4026b38b9 100644 --- a/src/internal/database/client.ts +++ b/src/internal/database/client.ts @@ -1,9 +1,9 @@ +import { Cluster } from '@internal/cluster' +import { ERRORS } from '@internal/errors' import { getConfig } from '../../config' -import { getTenantConfig } from './tenant' import { TenantConnection } from './connection' import { User } from './pool' -import { ERRORS } from '@internal/errors' -import { Cluster } from '@internal/cluster' +import { getTenantConfig } from './tenant' interface ConnectionOptions { host: string diff --git a/src/internal/database/connection.ts b/src/internal/database/connection.ts index 1eae6e031..1f27d54dc 100644 --- a/src/internal/database/connection.ts +++ b/src/internal/database/connection.ts @@ -1,14 +1,20 @@ -import pg, { DatabaseError } from 'pg' -import { Knex, knex } from 'knex' import retry from 'async-retry' +import { Knex, knex } from 'knex' +import pg, { DatabaseError } from 'pg' + import KnexTimeoutError = knex.KnexTimeoutError -import { ERRORS } from '@internal/errors' + import { - PoolStrategy, PoolManager, + PoolStrategy, searchPath, TenantConnectionOptions, } from '@internal/database/pool' +import { ERRORS } from '@internal/errors' +import { TransactionOptions } from '@storage/database' +import { getConfig } from '../../config' + +const { databaseStatementTimeout } = getConfig() // https://github.com/knex/knex/issues/387#issuecomment-51554522 pg.types.setTypeParser(20, 'text', parseInt) @@ -16,6 +22,7 @@ pg.types.setTypeParser(20, 'text', parseInt) export class TenantConnection { static poolManager = new PoolManager() public readonly role: string + private abortSignal?: AbortSignal constructor( public readonly pool: PoolStrategy, @@ -24,6 +31,14 @@ export class TenantConnection { this.role = options.user.payload.role || 'anon' } + setAbortSignal(signal: AbortSignal) { + this.abortSignal = signal + } + + getAbortSignal(): AbortSignal | undefined { + return this.abortSignal + } + static stop() { return TenantConnection.poolManager.destroyAll() } @@ -41,7 +56,7 @@ export class TenantConnection { return Promise.resolve() } - async transaction(instance?: Knex) { + async transaction(instance?: Knex, opts?: TransactionOptions): Promise { try { const tnx = await retry( async (bail) => { @@ -92,27 +107,64 @@ export class TenantConnection { } } + // Apply statement timeout at start of transaction (PgBouncer-compatible) + // SET LOCAL scopes the timeout to the current transaction only + if (typeof opts?.timeout !== 'undefined' || databaseStatementTimeout > 0) { + const statementTimeout = opts?.timeout ?? databaseStatementTimeout + + // Apply statement timeout if set + if (statementTimeout > 0) { + try { + await tnx.raw(`SET LOCAL statement_timeout TO '${statementTimeout}ms'`) + } catch (e) { + await tnx.rollback() + throw e + } + } + } + return tnx } catch (e) { if (e instanceof KnexTimeoutError) { throw ERRORS.DatabaseTimeout(e) } + // Handle pg client connectionTimeoutMillis timeout + // This fires when the connection timeout happens before pool acquisition timeout + if (e instanceof Error && e.message === 'timeout expired') { + throw ERRORS.DatabaseTimeout(e) + } + + // Handle database connection limit errors + if ( + e instanceof DatabaseError && + ((e.code === '08P01' && e.message.includes('no more connections allowed')) || + e.message.includes('Max client connections reached')) + ) { + throw ERRORS.DatabaseConnectionLimit(e) + } + throw e } } - transactionProvider(instance?: Knex): Knex.TransactionProvider { + transactionProvider(instance?: Knex, opts?: TransactionOptions): Knex.TransactionProvider { return async () => { - return this.transaction(instance) + return this.transaction(instance, opts) } } asSuperUser() { - return new TenantConnection(this.pool, { + const tenantConnection = new TenantConnection(this.pool, { ...this.options, user: this.options.superUser, }) + + if (this.abortSignal) { + tenantConnection.setAbortSignal(this.abortSignal) + } + + return tenantConnection } async setScope(tnx: Knex) { @@ -128,7 +180,8 @@ export class TenantConnection { set_config('request.headers', ?, true), set_config('request.method', ?, true), set_config('request.path', ?, true), - set_config('storage.operation', ?, true); + set_config('storage.operation', ?, true), + set_config('storage.allow_delete_query', 'true', true); `, [ this.role, diff --git a/src/internal/database/index.ts b/src/internal/database/index.ts index 2d3060a5b..9476aab62 100644 --- a/src/internal/database/index.ts +++ b/src/internal/database/index.ts @@ -1,5 +1,5 @@ -export * from './multitenant-db' -export * from './tenant' -export * from './connection' export * from './client' +export * from './connection' +export * from './multitenant-db' export * from './pubsub' +export * from './tenant' diff --git a/src/internal/database/migrations/files.test.ts b/src/internal/database/migrations/files.test.ts new file mode 100644 index 000000000..a0bca703a --- /dev/null +++ b/src/internal/database/migrations/files.test.ts @@ -0,0 +1,101 @@ +import { vi } from 'vitest' + +type FilesModule = typeof import('./files') + +type MigrationFile = { + id: number + name: string + hash: string + sql: string + contents: string +} + +function createMigration(id: number, name = `migration-${id}`): MigrationFile { + return { + id, + name, + hash: `hash-${id}`, + sql: `-- ${name}`, + contents: `-- ${name}`, + } +} + +async function loadFilesModule(loadMigrationFiles = vi.fn()) { + vi.resetModules() + + vi.doMock('postgres-migrations', () => ({ + loadMigrationFiles, + })) + + vi.doMock('../../../config', () => ({ + getConfig: () => ({ + dbMigrationFreezeAt: undefined, + }), + })) + + const files = (await import('./files')) as FilesModule + + return { + files, + loadMigrationFiles, + } +} + +describe('loadMigrationFilesCached', () => { + afterEach(() => { + vi.doUnmock('postgres-migrations') + vi.doUnmock('../../../config') + vi.resetModules() + vi.restoreAllMocks() + }) + + it('reuses the cached result for repeated calls to the same directory', async () => { + const migrations = [createMigration(1)] + const { files, loadMigrationFiles } = await loadFilesModule( + vi.fn().mockResolvedValue(migrations) + ) + + await expect(files.loadMigrationFilesCached('./migrations/tenant')).resolves.toEqual(migrations) + await expect(files.loadMigrationFilesCached('./migrations/tenant')).resolves.toEqual(migrations) + + expect(loadMigrationFiles).toHaveBeenCalledTimes(1) + expect(loadMigrationFiles).toHaveBeenCalledWith('./migrations/tenant') + }) + + it('shares the same in-flight promise for concurrent callers', async () => { + const migrations = [createMigration(1)] + let resolve!: (value: MigrationFile[]) => void + const pending = new Promise((innerResolve) => { + resolve = innerResolve + }) + const { files, loadMigrationFiles } = await loadFilesModule(vi.fn().mockReturnValue(pending)) + + const firstLoad = files.loadMigrationFilesCached('./migrations/tenant') + const secondLoad = files.loadMigrationFilesCached('./migrations/tenant') + + expect(loadMigrationFiles).toHaveBeenCalledTimes(1) + + resolve(migrations) + + await expect(Promise.all([firstLoad, secondLoad])).resolves.toEqual([migrations, migrations]) + }) + + it('keeps separate cache entries per directory', async () => { + const { files, loadMigrationFiles } = await loadFilesModule( + vi + .fn() + .mockResolvedValueOnce([createMigration(1, 'tenant-migration')]) + .mockResolvedValueOnce([createMigration(1, 'multitenant-migration')]) + ) + + await files.loadMigrationFilesCached('./migrations/tenant') + await files.loadMigrationFilesCached('./migrations/multitenant') + await files.loadMigrationFilesCached('./migrations/tenant') + + expect(loadMigrationFiles).toHaveBeenCalledTimes(2) + expect(loadMigrationFiles.mock.calls).toEqual([ + ['./migrations/tenant'], + ['./migrations/multitenant'], + ]) + }) +}) diff --git a/src/internal/database/migrations/files.ts b/src/internal/database/migrations/files.ts index 9281375a5..9456d3af2 100644 --- a/src/internal/database/migrations/files.ts +++ b/src/internal/database/migrations/files.ts @@ -5,52 +5,39 @@ import { getConfig } from '../../../config' const { dbMigrationFreezeAt } = getConfig() -export const loadMigrationFilesCached = memoizePromise(loadMigrationFiles) +const migrationFilesCache = new Map>() -export const localMigrationFiles = () => loadMigrationFiles('./migrations/tenant') +export function loadMigrationFilesCached(directory: string) { + let promise = migrationFilesCache.get(directory) -export async function lastLocalMigrationName() { - const migrations = await loadMigrationFilesCached('./migrations/tenant') - - if (!dbMigrationFreezeAt) { - return migrations[migrations.length - 1].name as keyof typeof DBMigration + if (!promise) { + promise = loadMigrationFiles(directory).catch((error) => { + migrationFilesCache.delete(directory) + throw error + }) + migrationFilesCache.set(directory, promise) } - const migrationIndex = migrations.findIndex((m) => m.name === dbMigrationFreezeAt) - if (migrationIndex === -1) { - throw ERRORS.InternalError(undefined, `Migration ${dbMigrationFreezeAt} not found`) - } - return migrations[migrationIndex].name as keyof typeof DBMigration + return promise } -/** - * Memoizes a promise - * @param func - */ -function memoizePromise( - func: (...args: Args) => Promise -): (...args: Args) => Promise { - const cache = new Map>() - - function generateKey(args: Args): string { - return args - .map((arg) => { - if (typeof arg === 'object' && arg !== null) { - return Object.entries(arg).sort().toString() - } - return String(arg) - }) - .join('|') +export const localMigrationFiles = () => loadMigrationFilesCached('./migrations/tenant') + +export async function lastLocalMigrationName() { + const migrations = await localMigrationFiles() + const latestMigration = migrations.at(-1) + + if (!latestMigration) { + throw ERRORS.InternalError(undefined, 'No local migrations found') } - return async function (...args: Args): Promise { - const key = generateKey(args) - if (cache.has(key)) { - return cache.get(key)! - } + if (!dbMigrationFreezeAt) { + return latestMigration.name as keyof typeof DBMigration + } - const result = func(...args) - cache.set(key, result) - return result + const frozenMigration = migrations.find((m) => m.name === dbMigrationFreezeAt) + if (!frozenMigration) { + throw ERRORS.InternalError(undefined, `Migration ${dbMigrationFreezeAt} not found`) } + return frozenMigration.name as keyof typeof DBMigration } diff --git a/src/internal/database/migrations/guards.ts b/src/internal/database/migrations/guards.ts new file mode 100644 index 000000000..f4d00162d --- /dev/null +++ b/src/internal/database/migrations/guards.ts @@ -0,0 +1,5 @@ +import { DBMigration } from './types' + +export function isDBMigrationName(value: unknown): value is keyof typeof DBMigration { + return typeof value === 'string' && Object.prototype.hasOwnProperty.call(DBMigration, value) +} diff --git a/src/internal/database/migrations/index.ts b/src/internal/database/migrations/index.ts index 9cbc2e390..727b5a6d9 100644 --- a/src/internal/database/migrations/index.ts +++ b/src/internal/database/migrations/index.ts @@ -1,3 +1,4 @@ -export * from './migrate' export * from './files' +export * from './guards' +export * from './migrate' export * from './types' diff --git a/src/internal/database/migrations/migrate.ts b/src/internal/database/migrations/migrate.ts index 72686ea56..b4a3c7d40 100644 --- a/src/internal/database/migrations/migrate.ts +++ b/src/internal/database/migrations/migrate.ts @@ -1,21 +1,22 @@ +import { ERRORS } from '@internal/errors' +import { ResetMigrationsOnTenant, RunMigrationsOnTenants } from '@storage/events' +import { Knex } from 'knex' import { Client, ClientConfig } from 'pg' -import SQL from 'sql-template-strings' import { MigrationError } from 'postgres-migrations' -import { getConfig, MultitenantMigrationStrategy } from '../../../config' -import { logger, logSchema } from '../../monitoring' +import { runMigration } from 'postgres-migrations/dist/run-migration' import { BasicPgClient, Migration } from 'postgres-migrations/dist/types' import { validateMigrationHashes } from 'postgres-migrations/dist/validation' -import { runMigration } from 'postgres-migrations/dist/run-migration' +import SQL from 'sql-template-strings' +import { getConfig, MultitenantMigrationStrategy } from '../../../config' +import { logger, logSchema } from '../../monitoring' +import { multitenantKnex } from '../multitenant-db' import { searchPath } from '../pool' +import { getSslSettings } from '../ssl' import { getTenantConfig, TenantMigrationStatus } from '../tenant' -import { multitenantKnex } from '../multitenant-db' +import { lastLocalMigrationName, loadMigrationFilesCached, localMigrationFiles } from './files' import { ProgressiveMigrations } from './progressive' -import { ResetMigrationsOnTenant, RunMigrationsOnTenants } from '@storage/events' -import { ERRORS } from '@internal/errors' +import { DisableConcurrentIndexTransformer, MigrationTransformer } from './transformers' import { DBMigration } from './types' -import { getSslSettings } from '../ssl' -import { MigrationTransformer, DisableConcurrentIndexTransformer } from './transformers' -import { lastLocalMigrationName, loadMigrationFilesCached, localMigrationFiles } from './files' const { isMultitenant, @@ -30,6 +31,8 @@ const { dbInstallRoles, dbRefreshMigrationHashesOnMismatch, dbMigrationFreezeAt, + icebergShards, + multitenantDatabaseQueryTimeout, } = getConfig() /** @@ -96,11 +99,7 @@ export async function tenantHasMigrations(tenantId: string, migration: keyof typ export async function* listTenantsToMigrate(signal: AbortSignal) { let lastCursor = 0 - while (true) { - if (signal.aborted) { - break - } - + while (!signal.aborted) { const migrationVersion = await lastLocalMigrationName() const data = await multitenantKnex @@ -137,11 +136,7 @@ export async function* listTenantsToResetMigrations( ) { let lastCursor = 0 - while (true) { - if (signal.aborted) { - break - } - + while (!signal.aborted) { const afterMigrations = Object.keys(DBMigration).filter((migrationName) => { return DBMigration[migrationName as keyof typeof DBMigration] > DBMigration[migration] }) @@ -170,11 +165,17 @@ export async function* listTenantsToResetMigrations( */ export async function updateTenantMigrationsState( tenantId: string, - options?: { migration?: keyof typeof DBMigration; state: TenantMigrationStatus } + options?: { + migration?: keyof typeof DBMigration + state: TenantMigrationStatus + tnx?: Knex.Transaction + } ) { const migrationVersion = options?.migration || (await lastLocalMigrationName()) const state = options?.state || TenantMigrationStatus.COMPLETED - return multitenantKnex + const db = options?.tnx ? options.tnx : multitenantKnex + + return db .table('tenants') .where('id', tenantId) .update({ @@ -186,6 +187,7 @@ export async function updateTenantMigrationsState( : migrationVersion, migrations_status: state, }) + .abortOnSignal(AbortSignal.timeout(multitenantDatabaseQueryTimeout)) } /** @@ -203,14 +205,17 @@ export async function areMigrationsUpToDate(tenantId: string) { ) } -export async function obtainLockOnMultitenantDB(fn: () => Promise) { +export async function obtainLockOnMultitenantDB(fn: (tnx: Knex.Transaction) => Promise) { + const trx = await multitenantKnex.transaction() try { - const result = await multitenantKnex.raw(`SELECT pg_try_advisory_lock(?);`, [ - '-8575985245963000605', - ]) - const lockAcquired = result.rows.shift()?.pg_try_advisory_lock || false + const result = await trx.raw( + `SELECT pg_try_advisory_xact_lock(?) AS locked;`, + [-8575985245963000605] + ) + const lockAcquired = result.rows.shift()?.locked || false if (!lockAcquired) { + await trx.rollback() return } @@ -218,11 +223,12 @@ export async function obtainLockOnMultitenantDB(fn: () => Promise) { type: 'migrations', }) - return await fn() - } finally { - try { - await multitenantKnex.raw(`SELECT pg_advisory_unlock(?);`, ['-8575985245963000605']) - } catch {} + const fnResult = await fn(trx) + await trx.commit() + return fnResult + } catch (e) { + await trx.rollback() + throw e } } @@ -303,6 +309,7 @@ export async function runMultitenantMigrations(): Promise { await connectAndMigrate({ databaseUrl: multitenantDatabaseUrl, migrationsDirectory: './migrations/multitenant', + migrationsTableSchema: 'public', shouldCreateStorageSchema: false, waitForLock: true, }) @@ -339,6 +346,7 @@ export async function runMigrationsOnTenant({ await connectAndMigrate({ databaseUrl, migrationsDirectory: './migrations/tenant', + migrationsTableSchema: 'storage', ssl: getSslSettings({ connectionString: databaseUrl, databaseSSLRootCert }), shouldCreateStorageSchema: true, tenantId, @@ -348,7 +356,7 @@ export async function runMigrationsOnTenant({ } export async function resetMigration(options: { - tenantId: string + tenantId?: string untilMigration: keyof typeof DBMigration markCompletedTillMigration?: keyof typeof DBMigration databaseUrl: string @@ -437,10 +445,12 @@ export async function resetMigration(options: { } } - await updateTenantMigrationsState(options.tenantId, { - migration: latestRunMigration, - state: TenantMigrationStatus.COMPLETED, - }) + if (options.tenantId) { + await updateTenantMigrationsState(options.tenantId, { + migration: latestRunMigration, + state: TenantMigrationStatus.COMPLETED, + }) + } await pgClient.query(`COMMIT`) @@ -469,7 +479,7 @@ async function connect(options: { const { ssl, tenantId, connectionString } = options const dbConfig: ClientConfig = { - connectionString: connectionString, + connectionString, connectionTimeoutMillis: 60_000, options: `-c search_path=${searchPath}`, ssl, @@ -494,6 +504,7 @@ async function connect(options: { async function connectAndMigrate(options: { databaseUrl: string | undefined migrationsDirectory: string + migrationsTableSchema?: string ssl?: ClientConfig['ssl'] shouldCreateStorageSchema?: boolean tenantId?: string @@ -517,6 +528,7 @@ async function connectAndMigrate(options: { await migrate({ client, migrationsDirectory, + migrationsTableSchema: options.migrationsTableSchema, waitForLock: Boolean(waitForLock), shouldCreateStorageSchema, upToMigration: options.upToMigration, @@ -529,6 +541,7 @@ async function connectAndMigrate(options: { interface MigrateOptions { client: BasicPgClient migrationsDirectory: string + migrationsTableSchema?: string waitForLock: boolean shouldCreateStorageSchema?: boolean upToMigration?: keyof typeof DBMigration @@ -544,6 +557,7 @@ interface MigrateOptions { export async function migrate({ client, migrationsDirectory, + migrationsTableSchema, waitForLock, shouldCreateStorageSchema, upToMigration, @@ -553,6 +567,7 @@ export async function migrate({ waitForLock, runMigrations({ migrationsDirectory, + migrationsTableSchema, shouldCreateStorageSchema, upToMigration, // Remove concurrent index creation if we're using oriole db as it does not support it currently @@ -563,6 +578,7 @@ export async function migrate({ interface RunMigrationOptions { migrationsDirectory: string + migrationsTableSchema?: string shouldCreateStorageSchema?: boolean upToMigration?: keyof typeof DBMigration transformers?: MigrationTransformer[] @@ -576,6 +592,7 @@ interface RunMigrationOptions { */ function runMigrations({ migrationsDirectory, + migrationsTableSchema, shouldCreateStorageSchema, upToMigration, transformers = [], @@ -599,10 +616,16 @@ function runMigrations({ await client.query(`SET search_path TO ${searchPath.join(',')}`) let appliedMigrations: Migration[] = [] - if (await doesTableExist(client, migrationTableName)) { + if ( + await doesTableExist({ + client, + schemaName: migrationsTableSchema, + tableName: migrationTableName, + }) + ) { const selectQueryCurrentMigration = SQL`SELECT * FROM ` .append(migrationTableName) - .append(SQL` WHERE id <= ${lastMigrationId}`) + .append(SQL` WHERE id <= ${lastMigrationId} ORDER BY id`) const { rows } = await client.query(selectQueryCurrentMigration) appliedMigrations = rows @@ -640,30 +663,66 @@ function runMigrations({ const migrationsToRun = filterMigrations(intendedMigrations, appliedMigrations) const completedMigrations = [] + const icebergShardVar = `{${icebergShards.map((s) => `"${s}"`).join(',')}}` + const icebergDefaultShard = icebergShards.length > 0 ? icebergShards[0] : '' + if (migrationsToRun.length > 0) { - await client.query(SQL`SELECT + await client.query(SQL`SELECT set_config('storage.install_roles', ${dbInstallRoles}, false), set_config('storage.multitenant', ${isMultitenant ? 'true' : 'false'}, false), set_config('storage.anon_role', ${dbAnonRole}, false), set_config('storage.authenticated_role', ${dbAuthenticatedRole}, false), set_config('storage.service_role', ${dbServiceRole}, false), - set_config('storage.super_user', ${dbSuperUser}, false) + set_config('storage.super_user', ${dbSuperUser}, false), + set_config('storage.iceberg_default_shard', ${icebergDefaultShard}, false), + set_config('storage.iceberg_shards', ${icebergShardVar}, false); `) } for (const migration of migrationsToRun) { - const result = await runMigration( - migrationTableName, - client - )(runMigrationTransformers(migration, transformers)) - completedMigrations.push(result) + try { + const ignore = migration.sql.includes('-- postgres-migrations ignore') + + if (ignore) { + ;(migration as any).sql = 'SELECT 1;' + ;(migration as any).contents = 'SELECT 1;' + } + const result = await runMigration( + migrationTableName, + client + )(runMigrationTransformers(migration, transformers)) + completedMigrations.push(result) + } catch (e) { + throw ERRORS.DatabaseError( + `Migration failed. Reason: ${(e as Error).message}`, + e as MigrationError + ).withMetadata({ + currentMigrations: appliedMigrations.map((migration) => ({ + id: migration.id, + name: migration.name, + hash: migration.hash, + })), + migrationsToRun: migrationsToRun.map((migration) => ({ + id: migration.id, + name: migration.name, + hash: migration.hash, + })), + migrationId: migration.id, + migrationName: migration.name, + migrationHash: migration.hash, + }) + } } return completedMigrations } catch (e) { - const error: MigrationError = new Error(`Migration failed. Reason: ${(e as Error).message}`) - error.cause = e + '' - throw error + if (e instanceof MigrationError) { + throw new MigrationError(`Migration failed. Reason: ${(e as Error).message}`, { + cause: e, + }) + } + + throw e } } } @@ -711,13 +770,26 @@ async function getDefaultAccessMethod(client: BasicPgClient): Promise { * @param client * @param tableName */ -async function doesTableExist(client: BasicPgClient, tableName: string) { - const result = await client.query(SQL`SELECT EXISTS ( +async function doesTableExist({ + client, + schemaName, + tableName, +}: { + client: BasicPgClient + schemaName?: string + tableName: string +}) { + const result = await client.query( + SQL`SELECT EXISTS ( SELECT 1 FROM pg_catalog.pg_class c + JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = ${tableName} AND c.relkind = 'r' -);`) +` + .append(schemaName ? SQL`AND n.nspname = ${schemaName}` : '') + .append(`);`) + ) return result.rows.length > 0 && result.rows[0].exists } @@ -734,7 +806,7 @@ async function doesSchemaExists(client: BasicPgClient, schemaName: string) { WHERE schema_name = ${schemaName} );`) - return result.rows.length > 0 && result.rows[0].exists === 'true' + return result.rows.length > 0 && result.rows[0].exists } /** @@ -874,16 +946,22 @@ async function refreshMigrationPosition( await client.query(`BEGIN`) try { await client.query(`DELETE FROM ${migrationTableName} WHERE id is not NULL`) + const query = SQL`INSERT INTO ` .append(migrationTableName) .append('(id, name, hash, executed_at) VALUES ') + newMigrations.forEach((migration) => { + console.log(`Migration applied: ${migration.id} - ${migration.name}`) + }) + newMigrations.forEach((migration, index) => { query.append(SQL`(${migration.id}, ${migration.name}, ${migration.hash}, NOW())`) if (index !== newMigrations.length - 1) { query.append(',') } }) + await client.query(query) await client.query(`COMMIT`) } catch (e) { diff --git a/src/internal/database/migrations/progressive.test.ts b/src/internal/database/migrations/progressive.test.ts new file mode 100644 index 000000000..fe4245f19 --- /dev/null +++ b/src/internal/database/migrations/progressive.test.ts @@ -0,0 +1,348 @@ +import { vi } from 'vitest' + +const { mockBatchSend, mockWarning, mockError } = vi.hoisted(() => ({ + mockBatchSend: vi.fn(), + mockWarning: vi.fn(), + mockError: vi.fn(), +})) + +vi.mock('../tenant', () => ({ + getTenantConfig: vi.fn(), + TenantMigrationStatus: { + FAILED_STALE: 'FAILED_STALE', + }, +})) + +vi.mock('@internal/database/migrations/migrate', () => ({ + areMigrationsUpToDate: vi.fn(), +})) + +vi.mock('@storage/events', () => ({ + RunMigrationsOnTenants: class { + static batchSend = mockBatchSend + payload: Record + + constructor(payload: Record) { + this.payload = payload + } + }, +})) + +vi.mock('../../monitoring', () => ({ + logger: {}, + logSchema: { + info: vi.fn(), + warning: mockWarning, + error: mockError, + }, +})) + +import { areMigrationsUpToDate } from '@internal/database/migrations/migrate' +import { ERRORS } from '@internal/errors' +import { RunMigrationsOnTenants } from '@storage/events' +import { getTenantConfig } from '../tenant' +import { ProgressiveMigrations } from './progressive' + +class TestProgressiveMigrations extends ProgressiveMigrations { + seed(...tenants: string[]) { + this.tenants.push(...tenants) + } + + pending() { + return [...this.tenants] + } + + isEmitting() { + return this.emittingJobs + } + + flush(maxJobs: number) { + return this.createJobs(maxJobs) + } +} + +const mockGetTenantConfig = vi.mocked(getTenantConfig) +const mockAreMigrationsUpToDate = vi.mocked(areMigrationsUpToDate) +const mockRunMigrationsBatchSend = vi.mocked(RunMigrationsOnTenants.batchSend) + +describe('ProgressiveMigrations', () => { + beforeEach(() => { + vi.clearAllMocks() + + mockGetTenantConfig.mockResolvedValue({ + migrationStatus: undefined, + syncMigrationsDone: false, + } as Awaited>) + mockAreMigrationsUpToDate.mockResolvedValue(false) + }) + + it('keeps queued tenants and resets emittingJobs when batchSend fails', async () => { + mockRunMigrationsBatchSend + .mockRejectedValueOnce(new Error('queue unavailable')) + .mockResolvedValueOnce(undefined as never) + + const migrations = new TestProgressiveMigrations({ + maxSize: 10, + interval: 1000, + watch: false, + }) + + migrations.seed('tenant-a') + + await expect(migrations.flush(1)).rejects.toThrow('queue unavailable') + expect(migrations.pending()).toEqual(['tenant-a']) + expect(migrations.isEmitting()).toBe(false) + + await expect(migrations.flush(1)).resolves.toBeUndefined() + expect(migrations.pending()).toEqual([]) + expect(migrations.isEmitting()).toBe(false) + expect(mockRunMigrationsBatchSend).toHaveBeenCalledTimes(2) + }) + + it('logs batch enqueue failures at the caller boundary', async () => { + mockRunMigrationsBatchSend.mockRejectedValueOnce(new Error('queue unavailable')) + + const migrations = new TestProgressiveMigrations({ + maxSize: 10, + interval: 1000, + watch: false, + }) + + migrations.seed('tenant-a') + + await expect(migrations.drain()).resolves.toBeUndefined() + + expect(migrations.pending()).toEqual(['tenant-a']) + expect(migrations.isEmitting()).toBe(false) + expect(mockError).toHaveBeenCalledTimes(1) + expect(mockError).toHaveBeenCalledWith( + expect.anything(), + '[Migrations] Error creating migration jobs', + expect.objectContaining({ + type: 'migrations', + }) + ) + }) + + it('keeps new tenants queued while a batch is in flight and ignores duplicate adds', async () => { + const deferredBatch = Promise.withResolvers() + mockRunMigrationsBatchSend.mockReturnValueOnce(deferredBatch.promise as never) + + const migrations = new TestProgressiveMigrations({ + maxSize: 10, + interval: 1000, + watch: false, + }) + + migrations.seed('tenant-a') + + const flushPromise = migrations.flush(1) + await new Promise((resolve) => setImmediate(resolve)) + + expect(mockRunMigrationsBatchSend).toHaveBeenCalledTimes(1) + expect(migrations.isEmitting()).toBe(true) + + migrations.addTenant('tenant-a') + migrations.addTenant('tenant-b') + + expect(migrations.pending()).toEqual(['tenant-a', 'tenant-b']) + + deferredBatch.resolve() + + await expect(flushPromise).resolves.toBeUndefined() + expect(migrations.pending()).toEqual(['tenant-b']) + expect(migrations.isEmitting()).toBe(false) + }) + + it('serializes drain with an in-flight batch and drains the remaining tenants after it finishes', async () => { + const deferredBatch = Promise.withResolvers() + mockRunMigrationsBatchSend + .mockReturnValueOnce(deferredBatch.promise as never) + .mockResolvedValueOnce(undefined as never) + + const migrations = new TestProgressiveMigrations({ + maxSize: 1, + interval: 1000, + watch: false, + }) + + migrations.seed('tenant-a', 'tenant-b') + + const flushPromise = migrations.flush(1) + await new Promise((resolve) => setImmediate(resolve)) + + const drainPromise = migrations.drain() + + expect(mockRunMigrationsBatchSend).toHaveBeenCalledTimes(1) + expect(migrations.isEmitting()).toBe(true) + + deferredBatch.resolve() + + await expect(Promise.all([flushPromise, drainPromise])).resolves.toEqual([undefined, undefined]) + + expect(mockRunMigrationsBatchSend).toHaveBeenCalledTimes(2) + expect( + (mockRunMigrationsBatchSend.mock.calls[0][0][0] as { payload: { tenantId: string } }).payload + ).toMatchObject({ + tenantId: 'tenant-a', + }) + expect( + (mockRunMigrationsBatchSend.mock.calls[1][0][0] as { payload: { tenantId: string } }).payload + ).toMatchObject({ + tenantId: 'tenant-b', + }) + expect(migrations.pending()).toEqual([]) + expect(migrations.isEmitting()).toBe(false) + }) + + it('starts a follow-up run when drain is requested in a late microtask after a batch settles', async () => { + const migrations = new TestProgressiveMigrations({ + maxSize: 1, + interval: 1000, + watch: false, + }) + + mockRunMigrationsBatchSend + .mockImplementationOnce(async () => { + queueMicrotask(() => { + migrations.addTenant('tenant-b') + void migrations.drain() + }) + }) + .mockResolvedValueOnce(undefined as never) + + migrations.seed('tenant-a') + + await expect(migrations.flush(1)).resolves.toBeUndefined() + await new Promise((resolve) => setImmediate(resolve)) + + expect(mockRunMigrationsBatchSend).toHaveBeenCalledTimes(2) + expect( + (mockRunMigrationsBatchSend.mock.calls[0][0][0] as { payload: { tenantId: string } }).payload + ).toMatchObject({ + tenantId: 'tenant-a', + }) + expect( + (mockRunMigrationsBatchSend.mock.calls[1][0][0] as { payload: { tenantId: string } }).payload + ).toMatchObject({ + tenantId: 'tenant-b', + }) + expect(migrations.pending()).toEqual([]) + expect(migrations.isEmitting()).toBe(false) + }) + + it('moves prep-failed tenants to the back so later tenants can still be scheduled', async () => { + mockGetTenantConfig.mockImplementation(async (tenantId) => { + if (tenantId === 'tenant-b') { + throw new Error('tenant lookup failed') + } + + return { + migrationStatus: undefined, + syncMigrationsDone: false, + } as Awaited> + }) + + const migrations = new TestProgressiveMigrations({ + maxSize: 1, + interval: 1000, + watch: false, + }) + + migrations.seed('tenant-b', 'tenant-a') + + await expect(migrations.flush(1)).resolves.toBeUndefined() + expect(migrations.pending()).toEqual(['tenant-a', 'tenant-b']) + + await expect(migrations.flush(1)).resolves.toBeUndefined() + expect(mockRunMigrationsBatchSend).toHaveBeenCalledTimes(1) + expect( + (mockRunMigrationsBatchSend.mock.calls[0][0][0] as { payload: { tenantId: string } }).payload + ).toMatchObject({ + tenantId: 'tenant-a', + }) + expect(migrations.pending()).toEqual(['tenant-b']) + }) + + it('keeps tenants queued when preparing a migration job fails', async () => { + mockGetTenantConfig.mockImplementation(async (tenantId) => { + if (tenantId === 'tenant-b') { + throw new Error('tenant lookup failed') + } + + return { + migrationStatus: undefined, + syncMigrationsDone: false, + } as Awaited> + }) + + const migrations = new TestProgressiveMigrations({ + maxSize: 10, + interval: 1000, + watch: false, + }) + + migrations.seed('tenant-a', 'tenant-b') + + await expect(migrations.flush(2)).resolves.toBeUndefined() + + expect(mockRunMigrationsBatchSend).toHaveBeenCalledTimes(1) + expect(mockRunMigrationsBatchSend.mock.calls[0][0]).toHaveLength(1) + expect( + (mockRunMigrationsBatchSend.mock.calls[0][0][0] as { payload: { tenantId: string } }).payload + ).toMatchObject({ + tenantId: 'tenant-a', + }) + expect(migrations.pending()).toEqual(['tenant-b']) + expect(migrations.isEmitting()).toBe(false) + expect(mockWarning).toHaveBeenCalledWith( + expect.anything(), + '[Migrations] Failed to prepare migration job for tenant tenant-b; keeping tenant queued for retry', + expect.objectContaining({ + type: 'migrations', + project: 'tenant-b', + }) + ) + }) + + it('drops tenants whose config no longer exists instead of retrying forever', async () => { + mockGetTenantConfig.mockImplementation(async (tenantId) => { + if (tenantId === 'tenant-b') { + throw ERRORS.MissingTenantConfig(tenantId) + } + + return { + migrationStatus: undefined, + syncMigrationsDone: false, + } as Awaited> + }) + + const migrations = new TestProgressiveMigrations({ + maxSize: 10, + interval: 1000, + watch: false, + }) + + migrations.seed('tenant-a', 'tenant-b') + + await expect(migrations.flush(2)).resolves.toBeUndefined() + + expect(mockRunMigrationsBatchSend).toHaveBeenCalledTimes(1) + expect(mockRunMigrationsBatchSend.mock.calls[0][0]).toHaveLength(1) + expect( + (mockRunMigrationsBatchSend.mock.calls[0][0][0] as { payload: { tenantId: string } }).payload + ).toMatchObject({ + tenantId: 'tenant-a', + }) + expect(migrations.pending()).toEqual([]) + expect(migrations.isEmitting()).toBe(false) + expect(mockWarning).toHaveBeenCalledWith( + expect.anything(), + '[Migrations] Failed to prepare migration job for tenant tenant-b; dropping tenant from queue because it no longer exists', + expect.objectContaining({ + type: 'migrations', + project: 'tenant-b', + }) + ) + }) +}) diff --git a/src/internal/database/migrations/progressive.ts b/src/internal/database/migrations/progressive.ts index 9208975fc..e4ca995f1 100644 --- a/src/internal/database/migrations/progressive.ts +++ b/src/internal/database/migrations/progressive.ts @@ -1,14 +1,17 @@ -import { logger, logSchema } from '../../monitoring' -import { getTenantConfig, TenantMigrationStatus } from '../tenant' -import { RunMigrationsOnTenants } from '@storage/events' import { areMigrationsUpToDate } from '@internal/database/migrations/migrate' +import { ErrorCode, isStorageError } from '@internal/errors' +import { RunMigrationsOnTenants } from '@storage/events' import { getConfig } from '../../../config' +import { logger, logSchema } from '../../monitoring' +import { getTenantConfig, TenantMigrationStatus } from '../tenant' const { dbMigrationFreezeAt } = getConfig() export class ProgressiveMigrations { protected tenants: string[] = [] protected emittingJobs = false + protected inFlightCreateJobs?: Promise + protected pendingCreateJobsMax = 0 protected watchInterval: NodeJS.Timeout | undefined constructor(protected readonly options: { maxSize: number; interval: number; watch?: boolean }) { @@ -96,9 +99,33 @@ export class ProgressiveMigrations { }, this.options.interval) } - protected async createJobs(maxJobs: number) { + protected createJobs(maxJobs: number) { + this.pendingCreateJobsMax = Math.max(this.pendingCreateJobsMax, maxJobs) + + if (this.inFlightCreateJobs) { + return this.inFlightCreateJobs + } + this.emittingJobs = true - const tenantsBatch = this.tenants.splice(0, maxJobs) + this.inFlightCreateJobs = this.runCreateJobs().finally(() => { + this.emittingJobs = false + this.inFlightCreateJobs = undefined + this.pendingCreateJobsMax = 0 + }) + + return this.inFlightCreateJobs + } + + protected async runCreateJobs() { + while (this.pendingCreateJobsMax > 0) { + const maxJobs = this.pendingCreateJobsMax + this.pendingCreateJobsMax = 0 + await this.createJobsBatch(maxJobs) + } + } + + protected async createJobsBatch(maxJobs: number) { + const tenantsBatch = this.tenants.slice(0, maxJobs) const jobs = await Promise.allSettled( tenantsBatch.map(async (tenant) => { const tenantConfig = await getTenantConfig(tenant) @@ -127,15 +154,68 @@ export class ProgressiveMigrations { }) ) + const completedTenants = new Set() + const droppedTenants = new Set() + const retryableFailedTenants = new Set() const validJobs = jobs - .map((job) => { - if (job.status === 'fulfilled' && job.value) { - return job.value + .map((job, index) => { + const tenant = tenantsBatch[index] + + if (job.status === 'rejected') { + // If there are more terminal errors later, we need to extend this check. + if (isStorageError(ErrorCode.TenantNotFound, job.reason)) { + droppedTenants.add(tenant) + logSchema.warning( + logger, + `[Migrations] Failed to prepare migration job for tenant ${tenant}; dropping tenant from queue because it no longer exists`, + { + type: 'migrations', + error: job.reason, + project: tenant, + metadata: JSON.stringify({ + strategy: 'progressive', + }), + } + ) + return + } + + retryableFailedTenants.add(tenant) + logSchema.warning( + logger, + `[Migrations] Failed to prepare migration job for tenant ${tenant}; keeping tenant queued for retry`, + { + type: 'migrations', + error: job.reason, + project: tenant, + metadata: JSON.stringify({ + strategy: 'progressive', + }), + } + ) + return } + + completedTenants.add(tenant) + return job.value }) .filter((job) => job) - await RunMigrationsOnTenants.batchSend(validJobs as RunMigrationsOnTenants[]) - this.emittingJobs = false + if (validJobs.length > 0) { + await RunMigrationsOnTenants.batchSend(validJobs as RunMigrationsOnTenants[]) + } + + if (completedTenants.size > 0 || droppedTenants.size > 0 || retryableFailedTenants.size > 0) { + const remainingTenants = this.tenants.filter( + (tenant) => + !completedTenants.has(tenant) && + !droppedTenants.has(tenant) && + !retryableFailedTenants.has(tenant) + ) + const failedTenantsInQueue = this.tenants.filter((tenant) => + retryableFailedTenants.has(tenant) + ) + this.tenants = remainingTenants.concat(failedTenantsInQueue) + } } } diff --git a/src/internal/database/migrations/transformers/disable-concurrent-index-transformer.test.ts b/src/internal/database/migrations/transformers/disable-concurrent-index-transformer.test.ts new file mode 100644 index 000000000..fe154b99b --- /dev/null +++ b/src/internal/database/migrations/transformers/disable-concurrent-index-transformer.test.ts @@ -0,0 +1,110 @@ +import { DisableConcurrentIndexTransformer } from '@internal/database/migrations/transformers' + +describe('DisableConcurrentIndexTransformer', () => { + const transformer = new DisableConcurrentIndexTransformer() + + it('should replace INDEX CONCURRENTLY with INDEX', () => { + const migration = { + id: 1, + name: 'test-migration', + hash: 'abc123', + sql: 'CREATE INDEX CONCURRENTLY idx_name ON table (column);', + contents: 'CREATE INDEX CONCURRENTLY idx_name ON table (column);', + fileName: 'test.sql', + } + + const result = transformer.transform(migration) + + expect(result.sql).toBe('CREATE INDEX idx_name ON table (column);') + expect(result.contents).toBe('CREATE INDEX idx_name ON table (column);') + expect(result.id).toBe(1) + expect(result.name).toBe('test-migration') + expect(result.hash).toBe('abc123') + }) + + it('should remove disable-transaction directive', () => { + const migration = { + id: 2, + name: 'test-migration-2', + hash: 'def456', + sql: '-- postgres-migrations disable-transaction\nCREATE INDEX CONCURRENTLY idx_name ON table (column);', + contents: + '-- postgres-migrations disable-transaction\nCREATE INDEX CONCURRENTLY idx_name ON table (column);', + fileName: 'test2.sql', + } + + const result = transformer.transform(migration) + + expect(result.sql).toBe('\nCREATE INDEX idx_name ON table (column);') + expect(result.contents).toBe('\nCREATE INDEX idx_name ON table (column);') + }) + + it('should handle migrations without CONCURRENTLY (no-op)', () => { + const migration = { + id: 3, + name: 'test-migration-3', + hash: 'ghi789', + sql: 'CREATE TABLE test_table (id SERIAL PRIMARY KEY);', + contents: 'CREATE TABLE test_table (id SERIAL PRIMARY KEY);', + fileName: 'test3.sql', + } + + const result = transformer.transform(migration) + + expect(result).toEqual(migration) + }) + + it('should handle multiple CONCURRENTLY occurrences', () => { + const migration = { + id: 4, + name: 'test-migration-4', + hash: 'jkl012', + sql: 'CREATE INDEX CONCURRENTLY idx1 ON table1 (col1);\nCREATE INDEX CONCURRENTLY idx2 ON table2 (col2);', + contents: + 'CREATE INDEX CONCURRENTLY idx1 ON table1 (col1);\nCREATE INDEX CONCURRENTLY idx2 ON table2 (col2);', + fileName: 'test4.sql', + } + + const result = transformer.transform(migration) + + expect(result.sql).toBe( + 'CREATE INDEX idx1 ON table1 (col1);\nCREATE INDEX idx2 ON table2 (col2);' + ) + expect(result.contents).toBe( + 'CREATE INDEX idx1 ON table1 (col1);\nCREATE INDEX idx2 ON table2 (col2);' + ) + }) + + it('should preserve migration structure', () => { + const migration = { + id: 5, + name: 'complex-migration', + hash: 'mno345', + sql: 'CREATE INDEX CONCURRENTLY idx_name ON table (column);', + contents: 'CREATE INDEX CONCURRENTLY idx_name ON table (column);', + fileName: 'complex.sql', + } + + const result = transformer.transform(migration) + + expect(result.id).toBe(migration.id) + expect(result.name).toBe(migration.name) + expect(result.hash).toBe(migration.hash) + expect(result.fileName).toBe(migration.fileName) + }) + + it('should handle edge cases (empty sql, no matches)', () => { + const migration = { + id: 6, + name: 'empty-migration', + hash: 'pqr678', + sql: '', + contents: '', + fileName: 'empty.sql', + } + + const result = transformer.transform(migration) + + expect(result).toEqual(migration) + }) +}) diff --git a/src/internal/database/migrations/transformers/index.ts b/src/internal/database/migrations/transformers/index.ts index 93e9c7a81..6d6bb8c23 100644 --- a/src/internal/database/migrations/transformers/index.ts +++ b/src/internal/database/migrations/transformers/index.ts @@ -1,2 +1,2 @@ -export * from './transformer' export * from './disable-concurrent-index-transformer' +export * from './transformer' diff --git a/src/internal/database/migrations/types.ts b/src/internal/database/migrations/types.ts index 5398e8963..651d4e091 100644 --- a/src/internal/database/migrations/types.ts +++ b/src/internal/database/migrations/types.ts @@ -1,14 +1,15 @@ export const DBMigration = { + 'create-migrations-table': 0, initialmigration: 1, - 'search-files-search-function': 2, - 'storage-schema': 3, - 'pathtoken-column': 4, - 'add-migrations-rls': 5, - 'add-size-functions': 6, - 'change-column-name-in-get-size': 7, - 'add-rls-to-buckets': 8, - 'add-public-to-buckets': 9, - 'fix-search-function': 10, + 'storage-schema': 2, + 'pathtoken-column': 3, + 'add-migrations-rls': 4, + 'add-size-functions': 5, + 'change-column-name-in-get-size': 6, + 'add-rls-to-buckets': 7, + 'add-public-to-buckets': 8, + 'fix-search-function': 9, + 'search-files-search-function': 10, 'add-trigger-to-auto-update-updated_at-column': 11, 'add-automatic-avif-detection-flag': 12, 'add-bucket-custom-limits': 13, @@ -37,4 +38,24 @@ export const DBMigration = { 'optimise-existing-functions': 36, 'add-bucket-name-length-trigger': 37, 'iceberg-catalog-flag-on-buckets': 38, -} + 'add-search-v2-sort-support': 39, + 'fix-prefix-race-conditions-optimized': 40, + 'add-object-level-update-trigger': 41, + 'rollback-prefix-triggers': 42, + 'fix-object-level': 43, + 'vector-bucket-type': 44, + 'vector-buckets': 45, + 'buckets-objects-grants': 46, + 'iceberg-table-metadata': 47, + 'iceberg-catalog-ids': 48, + 'buckets-objects-grants-postgres': 49, + 'search-v2-optimised': 50, + 'index-backward-compatible-search': 51, + 'drop-not-used-indexes-and-functions': 52, + 'drop-index-lower-name': 53, + 'drop-index-object-level': 54, + 'prevent-direct-deletes': 55, + 'fix-optimized-search-function': 56, + 's3-multipart-uploads-metadata': 57, + 'operation-ergonomics': 58, +} as const diff --git a/src/internal/database/multitenant-db.ts b/src/internal/database/multitenant-db.ts index cd947a42b..f0ebfa6f7 100644 --- a/src/internal/database/multitenant-db.ts +++ b/src/internal/database/multitenant-db.ts @@ -1,18 +1,28 @@ import Knex from 'knex' import { getConfig } from '../../config' -const { multitenantDatabaseUrl } = getConfig() +const { + multitenantDatabaseUrl, + multitenantDatabasePoolUrl, + multitenantMaxConnections, + databaseApplicationName, +} = getConfig() + +const poolSize = multitenantDatabasePoolUrl + ? multitenantMaxConnections * 10 + : multitenantMaxConnections export const multitenantKnex = Knex({ client: 'pg', connection: { - connectionString: multitenantDatabaseUrl, + connectionString: multitenantDatabasePoolUrl || multitenantDatabaseUrl, connectionTimeoutMillis: 5000, + application_name: databaseApplicationName, }, version: '12', pool: { min: 0, - max: 10, + max: poolSize, createTimeoutMillis: 5000, acquireTimeoutMillis: 5000, idleTimeoutMillis: 5000, diff --git a/src/internal/database/pool.test.ts b/src/internal/database/pool.test.ts new file mode 100644 index 000000000..f4287de7c --- /dev/null +++ b/src/internal/database/pool.test.ts @@ -0,0 +1,675 @@ +import { TENANT_POOL_CACHE_NAME } from '@internal/cache/names' +import { type Mock, vi } from 'vitest' +import type { PoolStrategy, TenantConnectionOptions } from './pool' + +type TestPool = { + acquire: Mock + rebalance: Mock + destroy: Mock<() => Promise> + getPoolStats: Mock +} + +type PoolModule = typeof import('./pool') + +function isTenantPoolCacheLookupCall(message: string) { + return (call: unknown[]) => call[1] === message +} + +function createPoolSettings(tenantId: string) { + return { + tenantId, + dbUrl: 'postgres://example', + maxConnections: 10, + user: { jwt: 'jwt', payload: { role: 'authenticated' } }, + superUser: { jwt: 'service', payload: { role: 'service_role' } }, + } +} + +function createTestPool(stats: { used: number; total: number } | null = null): TestPool { + return { + acquire: vi.fn(), + rebalance: vi.fn(), + destroy: vi.fn().mockResolvedValue(undefined), + getPoolStats: vi.fn().mockReturnValue(stats), + } +} + +async function loadPoolModule( + ttlMs: number, + maxEntries?: number, + configOverrides: Record = {} +): Promise { + vi.resetModules() + + const configModule = await import('../../config') + configModule.getConfig({ reload: true }) + configModule.mergeConfig({ + isMultitenant: true, + ...configOverrides, + } as Parameters[0]) + + const cacheOptionOverrides = { + ttl: ttlMs, + ...(maxEntries === undefined ? {} : { max: maxEntries }), + } + + vi.doMock('@internal/cache', async () => { + const actual = await vi.importActual('@internal/cache') + + return { + ...actual, + createTtlCache: ((optionsOrName: unknown, maybeOptions?: Record) => { + if (typeof optionsOrName === 'string') { + return actual.createTtlCache( + optionsOrName as never, + { + ...(maybeOptions || {}), + ...cacheOptionOverrides, + } as never + ) + } + + return actual.createTtlCache({ + ...(optionsOrName as Record), + ...cacheOptionOverrides, + } as never) + }) as typeof actual.createTtlCache, + } + }) + + return import('./pool') +} + +describe('PoolManager cache lifecycle', () => { + beforeAll(() => { + vi.useFakeTimers() + }) + + beforeEach(() => { + vi.clearAllTimers() + vi.setSystemTime(0) + }) + + afterEach(() => { + vi.doUnmock('@internal/cache') + vi.resetModules() + vi.restoreAllMocks() + }) + + afterAll(() => { + vi.useRealTimers() + }) + + test('expires cached pools and disposes them after inactivity', async () => { + const poolModule = await loadPoolModule(20) + + class TestPoolManager extends poolModule.PoolManager { + created: TestPool[] = [] + + protected newPool(_settings: TenantConnectionOptions): PoolStrategy { + const pool: TestPool = { + acquire: vi.fn(), + rebalance: vi.fn(), + destroy: vi.fn().mockResolvedValue(undefined), + getPoolStats: vi.fn().mockReturnValue(null), + } + this.created.push(pool) + return pool + } + } + + const poolManager = new TestPoolManager() + const settings = createPoolSettings('tenant-a') + + const first = poolManager.getPool(settings) + + expect(poolManager.created).toHaveLength(1) + + await vi.advanceTimersByTimeAsync(40) + + expect(poolManager.created[0].destroy).toHaveBeenCalledTimes(1) + + const second = poolManager.getPool(settings) + + expect(second).not.toBe(first) + expect(poolManager.created).toHaveLength(2) + + await poolManager.destroyAll() + }) + + test('refreshes pool ttl when an existing pool is reused', async () => { + const poolModule = await loadPoolModule(25) + + class TestPoolManager extends poolModule.PoolManager { + created: TestPool[] = [] + + protected newPool(_settings: TenantConnectionOptions): PoolStrategy { + const pool: TestPool = { + acquire: vi.fn(), + rebalance: vi.fn(), + destroy: vi.fn().mockResolvedValue(undefined), + getPoolStats: vi.fn().mockReturnValue(null), + } + this.created.push(pool) + return pool + } + } + + const poolManager = new TestPoolManager() + const settings = createPoolSettings('tenant-b') + + const first = poolManager.getPool(settings) + + await vi.advanceTimersByTimeAsync(15) + + const reused = poolManager.getPool(settings) + + expect(reused).toBe(first) + expect(poolManager.created[0].destroy).not.toHaveBeenCalled() + + await vi.advanceTimersByTimeAsync(15) + + expect(poolManager.created[0].destroy).not.toHaveBeenCalled() + + await vi.advanceTimersByTimeAsync(40) + + expect(poolManager.created[0].destroy).toHaveBeenCalledTimes(1) + + await poolManager.destroyAll() + }) + + test('records logical pool cache misses and hits', async () => { + const poolModule = await loadPoolModule(10_000) + const metricsModule = await import('@internal/monitoring/metrics') + const addSpy = vi.spyOn(metricsModule.cacheRequestsTotal, 'add') + + class TestPoolManager extends poolModule.PoolManager { + created: TestPool[] = [] + + protected newPool(_settings: TenantConnectionOptions): PoolStrategy { + const pool = createTestPool() + this.created.push(pool) + return pool + } + } + + const poolManager = new TestPoolManager() + const settings = createPoolSettings('tenant-cache-metrics') + + const first = poolManager.getPool(settings) + const second = poolManager.getPool(settings) + + expect(second).toBe(first) + expect(poolManager.created).toHaveLength(1) + expect(addSpy.mock.calls).toEqual( + expect.arrayContaining([ + [1, { cache: TENANT_POOL_CACHE_NAME, outcome: 'miss' }], + [1, { cache: TENANT_POOL_CACHE_NAME, outcome: 'hit' }], + ]) + ) + + await poolManager.destroyAll() + }) + + test('logs sampled tenant pool cache misses and hits', async () => { + const poolModule = await loadPoolModule(10_000, undefined, { + tenantPoolCacheHitLogSampleRate: 1, + tenantPoolCacheMissLogSampleRate: 1, + }) + const loggerModule = await import('@internal/monitoring/logger') + const infoSpy = vi.spyOn(loggerModule.logger, 'info').mockImplementation(() => undefined) + const logSchemaInfoSpy = vi.spyOn(loggerModule.logSchema, 'info') + + class TestPoolManager extends poolModule.PoolManager { + created: TestPool[] = [] + + protected newPool(_settings: TenantConnectionOptions): PoolStrategy { + const pool = createTestPool() + this.created.push(pool) + return pool + } + } + + const poolManager = new TestPoolManager() + const settings = createPoolSettings('tenant-cache-lookup-logs') + + const first = poolManager.getPool(settings) + const second = poolManager.getPool(settings) + + const expectedMissLog = expect.objectContaining({ + type: poolModule.TENANT_POOL_CACHE_LOOKUP_LOG_TYPE, + cache: TENANT_POOL_CACHE_NAME, + tenantId: 'tenant-cache-lookup-logs', + project: 'tenant-cache-lookup-logs', + outcome: 'miss', + sampleRate: 1, + sampleWeight: 1, + isCacheable: true, + isExternalPool: false, + isSingleUse: false, + }) + const expectedHitLog = expect.objectContaining({ + type: poolModule.TENANT_POOL_CACHE_LOOKUP_LOG_TYPE, + cache: TENANT_POOL_CACHE_NAME, + tenantId: 'tenant-cache-lookup-logs', + project: 'tenant-cache-lookup-logs', + outcome: 'hit', + sampleRate: 1, + sampleWeight: 1, + isCacheable: true, + isExternalPool: false, + isSingleUse: false, + }) + + expect(second).toBe(first) + expect(poolManager.created).toHaveLength(1) + expect(logSchemaInfoSpy.mock.calls).toEqual( + expect.arrayContaining([ + [loggerModule.logger, poolModule.TENANT_POOL_CACHE_LOOKUP_LOG_MESSAGE, expectedMissLog], + [loggerModule.logger, poolModule.TENANT_POOL_CACHE_LOOKUP_LOG_MESSAGE, expectedHitLog], + ]) + ) + expect(infoSpy.mock.calls).toEqual( + expect.arrayContaining([ + [expectedMissLog, poolModule.TENANT_POOL_CACHE_LOOKUP_LOG_MESSAGE], + [expectedHitLog, poolModule.TENANT_POOL_CACHE_LOOKUP_LOG_MESSAGE], + ]) + ) + + await poolManager.destroyAll() + }) + + test('does not log tenant pool cache lookups by default', async () => { + const poolModule = await loadPoolModule(10_000) + const loggerModule = await import('@internal/monitoring/logger') + const infoSpy = vi.spyOn(loggerModule.logger, 'info').mockImplementation(() => undefined) + + class TestPoolManager extends poolModule.PoolManager { + created: TestPool[] = [] + + protected newPool(_settings: TenantConnectionOptions): PoolStrategy { + const pool = createTestPool() + this.created.push(pool) + return pool + } + } + + const poolManager = new TestPoolManager() + const settings = createPoolSettings('tenant-cache-lookup-logs-disabled') + + poolManager.getPool(settings) + poolManager.getPool(settings) + + expect( + infoSpy.mock.calls.filter( + isTenantPoolCacheLookupCall(poolModule.TENANT_POOL_CACHE_LOOKUP_LOG_MESSAGE) + ) + ).toEqual([]) + + await poolManager.destroyAll() + }) + + test('does not log tenant pool cache lookups when sample rates are explicitly disabled', async () => { + const poolModule = await loadPoolModule(10_000, undefined, { + tenantPoolCacheHitLogSampleRate: 0, + tenantPoolCacheMissLogSampleRate: 0, + }) + const loggerModule = await import('@internal/monitoring/logger') + const infoSpy = vi.spyOn(loggerModule.logger, 'info').mockImplementation(() => undefined) + + class TestPoolManager extends poolModule.PoolManager { + created: TestPool[] = [] + + protected newPool(_settings: TenantConnectionOptions): PoolStrategy { + const pool = createTestPool() + this.created.push(pool) + return pool + } + } + + const poolManager = new TestPoolManager() + const settings = createPoolSettings('tenant-cache-lookup-logs-explicitly-disabled') + + poolManager.getPool(settings) + poolManager.getPool(settings) + + expect( + infoSpy.mock.calls.filter( + isTenantPoolCacheLookupCall(poolModule.TENANT_POOL_CACHE_LOOKUP_LOG_MESSAGE) + ) + ).toEqual([]) + + await poolManager.destroyAll() + }) + + test('does not log single-use external pool lookups without a cached pool', async () => { + const poolModule = await loadPoolModule(10_000) + const loggerModule = await import('@internal/monitoring/logger') + const infoSpy = vi.spyOn(loggerModule.logger, 'info').mockImplementation(() => undefined) + + class TestPoolManager extends poolModule.PoolManager { + created: TestPool[] = [] + + protected newPool(_settings: TenantConnectionOptions): PoolStrategy { + const pool = createTestPool() + this.created.push(pool) + return pool + } + } + + const poolManager = new TestPoolManager() + const pool = poolManager.getPool({ + ...createPoolSettings('tenant-single-use-external-log'), + isSingleUse: true, + isExternalPool: true, + }) + + expect( + infoSpy.mock.calls.filter( + isTenantPoolCacheLookupCall(poolModule.TENANT_POOL_CACHE_LOOKUP_LOG_MESSAGE) + ) + ).toEqual([]) + + await pool.destroy() + await poolManager.destroyAll() + }) + + test('records pool cache evictions when inactivity ttl removes cached pools', async () => { + const poolModule = await loadPoolModule(20) + const metricsModule = await import('@internal/monitoring/metrics') + const evictionSpy = vi.spyOn(metricsModule.cacheEvictionsTotal, 'add') + + class TestPoolManager extends poolModule.PoolManager { + created: TestPool[] = [] + + protected newPool(_settings: TenantConnectionOptions): PoolStrategy { + const pool = createTestPool() + this.created.push(pool) + return pool + } + } + + const poolManager = new TestPoolManager() + poolManager.getPool(createPoolSettings('tenant-cache-ttl-eviction')) + + await vi.advanceTimersByTimeAsync(40) + + expect(evictionSpy).toHaveBeenCalledWith(1, { + cache: TENANT_POOL_CACHE_NAME, + }) + expect(poolManager.created[0].destroy).toHaveBeenCalledTimes(1) + + await poolManager.destroyAll() + }) + + test('records pool cache evictions when capacity removes cached pools', async () => { + const poolModule = await loadPoolModule(10_000, 1) + const metricsModule = await import('@internal/monitoring/metrics') + const evictionSpy = vi.spyOn(metricsModule.cacheEvictionsTotal, 'add') + + class TestPoolManager extends poolModule.PoolManager { + created: TestPool[] = [] + + protected newPool(_settings: TenantConnectionOptions): PoolStrategy { + const pool = createTestPool() + this.created.push(pool) + return pool + } + } + + const poolManager = new TestPoolManager() + poolManager.getPool(createPoolSettings('tenant-cache-capacity-eviction-a')) + poolManager.getPool(createPoolSettings('tenant-cache-capacity-eviction-b')) + + expect(evictionSpy).toHaveBeenCalledWith(1, { + cache: TENANT_POOL_CACHE_NAME, + }) + expect(poolManager.created[0].destroy).toHaveBeenCalledTimes(1) + + await poolManager.destroyAll() + }) + + test('does not record pool cache evictions for explicit destroys', async () => { + const poolModule = await loadPoolModule(10_000) + const metricsModule = await import('@internal/monitoring/metrics') + const evictionSpy = vi.spyOn(metricsModule.cacheEvictionsTotal, 'add') + + class TestPoolManager extends poolModule.PoolManager { + created: TestPool[] = [] + + protected newPool(_settings: TenantConnectionOptions): PoolStrategy { + const pool = createTestPool() + this.created.push(pool) + return pool + } + } + + const poolManager = new TestPoolManager() + poolManager.getPool(createPoolSettings('tenant-cache-explicit-destroy-a')) + poolManager.getPool(createPoolSettings('tenant-cache-explicit-destroy-b')) + + await poolManager.destroy('tenant-cache-explicit-destroy-a') + await poolManager.destroyAll() + + expect(evictionSpy.mock.calls).not.toContainEqual([ + 1, + { + cache: TENANT_POOL_CACHE_NAME, + }, + ]) + expect(poolManager.created[0].destroy).toHaveBeenCalledTimes(1) + expect(poolManager.created[1].destroy).toHaveBeenCalledTimes(1) + }) + + test('does not record pool cache misses for single-use external pools without cached pools', async () => { + const poolModule = await loadPoolModule(10_000) + const metricsModule = await import('@internal/monitoring/metrics') + const addSpy = vi.spyOn(metricsModule.cacheRequestsTotal, 'add') + + class TestPoolManager extends poolModule.PoolManager { + created: TestPool[] = [] + + protected newPool(_settings: TenantConnectionOptions): PoolStrategy { + const pool = createTestPool() + this.created.push(pool) + return pool + } + } + + const poolManager = new TestPoolManager() + const settings = { + ...createPoolSettings('tenant-single-use-external'), + isSingleUse: true, + isExternalPool: true, + } + + const first = poolManager.getPool(settings) + const second = poolManager.getPool(settings) + + expect(second).not.toBe(first) + expect(poolManager.created).toHaveLength(2) + expect( + addSpy.mock.calls.filter(([, attrs]) => { + return attrs && typeof attrs === 'object' && attrs.cache === TENANT_POOL_CACHE_NAME + }) + ).toEqual([]) + + await Promise.all([first.destroy(), second.destroy()]) + await poolManager.destroyAll() + }) + + test('reuses cached pools for single-use external requests and records a hit', async () => { + const poolModule = await loadPoolModule(10_000) + const metricsModule = await import('@internal/monitoring/metrics') + const addSpy = vi.spyOn(metricsModule.cacheRequestsTotal, 'add') + + class TestPoolManager extends poolModule.PoolManager { + created: TestPool[] = [] + + protected newPool(_settings: TenantConnectionOptions): PoolStrategy { + const pool = createTestPool() + this.created.push(pool) + return pool + } + } + + const poolManager = new TestPoolManager() + const tenantId = 'tenant-single-use-external-reuses-cache' + const cachedPool = poolManager.getPool(createPoolSettings(tenantId)) + addSpy.mockClear() + + const reusedPool = poolManager.getPool({ + ...createPoolSettings(tenantId), + isSingleUse: true, + isExternalPool: true, + }) + + expect(reusedPool).toBe(cachedPool) + expect(poolManager.created).toHaveLength(1) + expect(addSpy.mock.calls).toEqual([[1, { cache: TENANT_POOL_CACHE_NAME, outcome: 'hit' }]]) + + await poolManager.destroyAll() + }) + + test('iterates cached pools for monitor snapshots', async () => { + const poolModule = await loadPoolModule(10_000) + const metricsModule = await import('@internal/monitoring/metrics') + const addBatchObservableCallbackSpy = vi.spyOn( + metricsModule.meter, + 'addBatchObservableCallback' + ) + let batchObserver: ((observer: { observe: (...args: unknown[]) => void }) => void) | undefined + + addBatchObservableCallbackSpy.mockImplementation((callback) => { + batchObserver = callback as typeof batchObserver + return undefined as never + }) + + class TestPoolManager extends poolModule.PoolManager { + created: Record = {} + + protected newPool(settings: TenantConnectionOptions): PoolStrategy { + const pool = createTestPool( + settings.tenantId === 'tenant-a' ? { used: 2, total: 5 } : { used: 3, total: 7 } + ) + this.created[settings.tenantId] = pool + return pool + } + } + + const poolManager = new TestPoolManager() + const firstPool = poolManager.getPool(createPoolSettings('tenant-a')) + poolManager.getPool(createPoolSettings('tenant-b')) + + poolManager.monitor() + await vi.advanceTimersByTimeAsync(5_000) + + const observeSpy = vi.fn() + batchObserver?.({ observe: observeSpy }) + + expect(observeSpy).toHaveBeenCalledWith(metricsModule.dbActivePool, 2) + expect(observeSpy).toHaveBeenCalledWith(metricsModule.dbActiveConnection, 12) + expect(observeSpy).toHaveBeenCalledWith(metricsModule.dbInUseConnection, 5) + + await vi.advanceTimersByTimeAsync(20_000) + + const recreatedPool = poolManager.getPool(createPoolSettings('tenant-a')) + + expect(recreatedPool).not.toBe(firstPool) + await vi.waitFor(() => { + expect(firstPool.destroy).toHaveBeenCalledTimes(1) + }) + + await poolManager.destroyAll() + }) + + test('iterates cached pools for rebalanceAll and destroyAll', async () => { + const poolModule = await loadPoolModule(10_000) + + class TestPoolManager extends poolModule.PoolManager { + created: Record = {} + + protected newPool(settings: TenantConnectionOptions): PoolStrategy { + const pool = createTestPool() + this.created[settings.tenantId] = pool + return pool + } + } + + const poolManager = new TestPoolManager() + const first = poolManager.getPool(createPoolSettings('tenant-c')) + const second = poolManager.getPool(createPoolSettings('tenant-d')) + + poolManager.rebalanceAll({ clusterSize: 4 }) + + expect(first.rebalance).toHaveBeenCalledWith({ clusterSize: 4 }) + expect(second.rebalance).toHaveBeenCalledWith({ clusterSize: 4 }) + + await poolManager.destroyAll() + + expect(first.destroy).toHaveBeenCalledTimes(1) + expect(second.destroy).toHaveBeenCalledTimes(1) + + const recreated = poolManager.getPool(createPoolSettings('tenant-c')) + + expect(recreated).not.toBe(first) + }) + + test('propagates explicit destroy failures without double-destroying pools', async () => { + const poolModule = await loadPoolModule(10_000) + + class TestPoolManager extends poolModule.PoolManager { + created: Record = {} + + protected newPool(settings: TenantConnectionOptions): PoolStrategy { + const pool = createTestPool() + pool.destroy.mockRejectedValue(new Error(`destroy failed for ${settings.tenantId}`)) + this.created[settings.tenantId] = pool + return pool + } + } + + const poolManager = new TestPoolManager() + const tenantId = 'tenant-destroy-error' + + poolManager.getPool(createPoolSettings(tenantId)) + + await expect(poolManager.destroy(tenantId)).rejects.toThrow(`destroy failed for ${tenantId}`) + expect(poolManager.created[tenantId].destroy).toHaveBeenCalledTimes(1) + }) + + test('preserves rejected destroyAll settlements when pool teardown fails', async () => { + const poolModule = await loadPoolModule(10_000) + + class TestPoolManager extends poolModule.PoolManager { + created: Record = {} + + protected newPool(settings: TenantConnectionOptions): PoolStrategy { + const pool = createTestPool() + + if (settings.tenantId === 'tenant-destroyall-error') { + pool.destroy.mockRejectedValue(new Error('destroyAll failed')) + } + + this.created[settings.tenantId] = pool + return pool + } + } + + const poolManager = new TestPoolManager() + poolManager.getPool(createPoolSettings('tenant-destroyall-ok')) + poolManager.getPool(createPoolSettings('tenant-destroyall-error')) + + const results = await poolManager.destroyAll() + const rejected = results.find((result) => result.status === 'rejected') + + expect(results).toHaveLength(2) + expect(rejected).toBeDefined() + expect(rejected).toMatchObject({ + status: 'rejected', + reason: expect.objectContaining({ message: 'destroyAll failed' }), + }) + expect(poolManager.created['tenant-destroyall-ok'].destroy).toHaveBeenCalledTimes(1) + expect(poolManager.created['tenant-destroyall-error'].destroy).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/internal/database/pool.ts b/src/internal/database/pool.ts index cf902cefa..9219fd74c 100644 --- a/src/internal/database/pool.ts +++ b/src/internal/database/pool.ts @@ -1,14 +1,21 @@ -import { getConfig } from '../../config' -import TTLCache from '@isaacs/ttlcache' -import { knex, Knex } from 'knex' -import { logger, logSchema } from '@internal/monitoring' -import { getSslSettings } from '@internal/database/ssl' +import { type CacheLookupOutcome, createTtlCache, TENANT_POOL_CACHE_NAME } from '@internal/cache' import { wait } from '@internal/concurrency' +import { getSslSettings } from '@internal/database/ssl' +import { logger, logSchema } from '@internal/monitoring' +import { + cacheEvictionsTotal, + cacheRequestsTotal, + dbActiveConnection, + dbActivePool, + dbInUseConnection, + isMetricEnabled, + meter, +} from '@internal/monitoring/metrics' import { JWTPayload } from 'jose' -import { DbActivePool } from '@internal/monitoring/metrics' +import { Knex, knex } from 'knex' +import { getConfig } from '../../config' const { - region, isMultitenant, databaseSSLRootCert, databaseMaxConnections, @@ -16,8 +23,14 @@ const { databaseConnectionTimeout, dbSearchPath, dbPostgresVersion, + databaseApplicationName, + tenantPoolCacheHitLogSampleRate, + tenantPoolCacheMissLogSampleRate, } = getConfig() +export const TENANT_POOL_CACHE_LOOKUP_LOG_TYPE = 'cache' +export const TENANT_POOL_CACHE_LOOKUP_LOG_MESSAGE = '[Cache] Tenant pool lookup' + export interface TenantConnectionOptions { user: User superUser: User @@ -30,6 +43,7 @@ export interface TenantConnectionOptions { reapIntervalMillis?: number maxConnections: number clusterSize?: number + numWorkers?: number headers?: Record method?: string path?: string @@ -41,58 +55,194 @@ export interface User { payload: { role?: string } & JWTPayload } +export interface PoolStats { + used: number + total: number +} + export interface PoolStrategy { acquire(): Knex rebalance(options: { clusterSize: number }): void destroy(): Promise + getPoolStats(): PoolStats | null } export const searchPath = ['storage', 'public', 'extensions', ...dbSearchPath.split(',')].filter( Boolean ) -const multiTenantLRUConfig = { +const multiTenantTtlConfig = { ttl: 1000 * 10, updateAgeOnGet: true, checkAgeOnGet: true, } -const tenantPools = new TTLCache({ - ...(isMultitenant ? multiTenantLRUConfig : { max: 1, ttl: Infinity }), - dispose: async (pool) => { - if (!pool) return - try { - await pool.destroy() - } catch (e) { - logSchema.error(logger, 'pool was not able to be destroyed', { - type: 'db', - error: e, - }) +const manuallyDestroyedPools = new WeakSet() + +function logPoolDestroyError(error: unknown): void { + logSchema.error(logger, 'pool was not able to be destroyed', { + type: 'db', + error, + }) +} + +async function destroyPool(pool: PoolStrategy): Promise { + await pool.destroy() +} + +async function destroyPoolSafely(pool: PoolStrategy): Promise { + try { + await destroyPool(pool) + } catch (e) { + logPoolDestroyError(e) + } +} + +function recordTenantPoolCacheEviction(reason: string): void { + // Explicit destroy paths are filtered before this helper is called. + if (reason === 'stale' || reason === 'evict' || reason === 'delete') { + cacheEvictionsTotal.add(1, { + cache: TENANT_POOL_CACHE_NAME, + }) + } +} + +function recordTenantPoolCacheRequest(outcome: string): void { + cacheRequestsTotal.add(1, { + cache: TENANT_POOL_CACHE_NAME, + outcome, + }) +} + +function shouldLogTenantPoolCacheLookup(sampleRate: number): boolean { + return sampleRate >= 1 || (sampleRate > 0 && Math.random() < sampleRate) +} + +function logTenantPoolCacheLookup( + settings: TenantConnectionOptions, + isCacheable: boolean, + outcome: CacheLookupOutcome +): void { + const sampleRate = + outcome === 'hit' ? tenantPoolCacheHitLogSampleRate : tenantPoolCacheMissLogSampleRate + + if (!shouldLogTenantPoolCacheLookup(sampleRate)) { + return + } + + const log = { + type: TENANT_POOL_CACHE_LOOKUP_LOG_TYPE, + cache: TENANT_POOL_CACHE_NAME, + tenantId: settings.tenantId, + project: settings.tenantId, + outcome, + sampleRate, + sampleWeight: 1 / sampleRate, + isCacheable, + isExternalPool: Boolean(settings.isExternalPool), + isSingleUse: Boolean(settings.isSingleUse), + } + + logSchema.info(logger, TENANT_POOL_CACHE_LOOKUP_LOG_MESSAGE, log) +} + +const tenantPools = createTtlCache({ + ...(isMultitenant ? multiTenantTtlConfig : { max: 1, ttl: Infinity }), + dispose: async (pool, _tenantId, reason) => { + if (!pool || manuallyDestroyedPools.has(pool)) { + return } + + recordTenantPoolCacheEviction(reason) + + await destroyPoolSafely(pool) }, }) +// ============================================================================ +// Pool stats collection — chunked to avoid blocking the event loop +// ============================================================================ +interface PoolStatsSnapshot { + poolCount: number + totalConnections: number + totalInUse: number +} + +const STATS_CHUNK_SIZE = 100 +const STATS_INTERVAL_MS = 5_000 + +let cachedPoolStats: PoolStatsSnapshot = { + poolCount: 0, + totalConnections: 0, + totalInUse: 0, +} +let collectInProgress = false + +async function collectPoolStats() { + if (collectInProgress) return + collectInProgress = true + + try { + let poolCount = 0 + let totalConnections = 0 + let totalInUse = 0 + let chunkCount = 0 + + for (const [, pool] of tenantPools.entries()) { + poolCount++ + const stats = pool.getPoolStats() + if (stats) { + totalConnections += stats.total + totalInUse += stats.used + } + // Yield to the event loop between chunks + if (++chunkCount % STATS_CHUNK_SIZE === 0) { + await new Promise((resolve) => setImmediate(resolve)) + } + } + + cachedPoolStats = { + poolCount, + totalConnections, + totalInUse, + } + } finally { + collectInProgress = false + } +} + /** * PoolManager is a class that manages a pool of Knex connections. * It creates a new pool for each tenant and reuses existing pools. */ export class PoolManager { - monitor(signal: AbortSignal) { - const monitorInterval = setInterval(() => { - DbActivePool.set( - { - region, - }, - tenantPools.size - ) - }, 2000) + protected numWorkers: number = 1 + + setNumWorkers(numWorkers: number) { + this.numWorkers = Math.max(numWorkers ?? 1, 1) + } - signal.addEventListener( - 'abort', - () => { - clearInterval(monitorInterval) + monitor() { + // Periodically collect stats in a non-blocking way + const interval = setInterval(() => { + void collectPoolStats() + }, STATS_INTERVAL_MS) + interval.unref() + + // Observable callback reads the cached snapshot — O(1) + meter.addBatchObservableCallback( + (observer) => { + if (isMetricEnabled('db_active_local_pools')) { + observer.observe(dbActivePool, cachedPoolStats.poolCount) + } + if (isMetricEnabled('db_connections')) { + observer.observe(dbActiveConnection, cachedPoolStats.totalConnections) + } + if (isMetricEnabled('db_connections_in_use')) { + observer.observe(dbInUseConnection, cachedPoolStats.totalInUse) + } }, - { once: true } + [dbActivePool, dbActiveConnection, dbInUseConnection] ) } @@ -114,24 +264,37 @@ export class PoolManager { } getPool(settings: TenantConnectionOptions) { - const existingPool = tenantPools.get(settings.tenantId) + const isCacheable = (settings.isSingleUse && !settings.isExternalPool) || !settings.isSingleUse + const { value: existingPool, outcome } = tenantPools.getWithOutcome(settings.tenantId) + if (existingPool) { + recordTenantPoolCacheRequest(outcome) + logTenantPoolCacheLookup(settings, isCacheable, outcome) + return existingPool } - const newPool = this.newPool(settings) - - if ((settings.isSingleUse && !settings.isExternalPool) || !settings.isSingleUse) { - tenantPools.set(settings.tenantId, newPool) + if (!isCacheable) { + return this.newPool({ ...settings, numWorkers: this.numWorkers }) } + + recordTenantPoolCacheRequest(outcome) + logTenantPoolCacheLookup(settings, isCacheable, outcome) + + const newPool = this.newPool({ ...settings, numWorkers: this.numWorkers }) + + tenantPools.set(settings.tenantId, newPool) return newPool } destroy(tenantId: string) { const pool = tenantPools.get(tenantId) if (pool) { + manuallyDestroyedPools.add(pool) tenantPools.delete(tenantId) - return pool.destroy() + return destroyPool(pool).finally(() => { + manuallyDestroyedPools.delete(pool) + }) } return Promise.resolve() } @@ -140,13 +303,18 @@ export class PoolManager { const promises: Promise[] = [] for (const [connectionString, pool] of tenantPools) { - promises.push(pool.destroy()) + manuallyDestroyedPools.add(pool) tenantPools.delete(connectionString) + promises.push( + destroyPool(pool).finally(() => { + manuallyDestroyedPools.delete(pool) + }) + ) } return Promise.allSettled(promises) } - protected newPool(settings: TenantConnectionOptions) { + protected newPool(settings: TenantConnectionOptions): PoolStrategy { return new TenantPool(settings) } } @@ -180,14 +348,25 @@ class TenantPool implements PoolStrategy { return this.drainPool(originalPool) } + getPoolStats(): PoolStats | null { + const tarnPool = this.pool?.client?.pool + if (!tarnPool) return null + return { + used: tarnPool.numUsed(), + total: tarnPool.numUsed() + tarnPool.numFree(), + } + } + getSettings() { const isSingleUseExternalPool = this.options.isSingleUse && this.options.isExternalPool + const numWorkers = Math.max(this.options.numWorkers ?? 1, 1) const clusterSize = this.options.clusterSize || 0 let maxConnection = this.options.maxConnections || databaseMaxConnections - if (clusterSize > 0) { - maxConnection = Math.ceil(maxConnection / clusterSize) + const divisor = Math.max(clusterSize, 1) * numWorkers + if (divisor > 1) { + maxConnection = Math.ceil(maxConnection / divisor) || 1 } if (isSingleUseExternalPool) { @@ -215,23 +394,16 @@ class TenantPool implements PoolStrategy { if (originalPool) { this.drainPool(originalPool).catch((e) => { - logger.error({ type: 'pool', error: e }) + logSchema.error(logger, 'Error draining tenant pool', { + type: 'pool', + error: e, + }) }) } } protected async drainPool(pool: Knex) { - if (!pool?.client?.pool) { - if (pool) return pool.destroy() - return - } - - while (true) { - if (!pool?.client?.pool) { - if (pool) return pool.destroy() - return - } - + for (; pool?.client?.pool; ) { let waiting = 0 waiting += pool.client.pool.numPendingAcquires() waiting += pool.client.pool.numPendingValidations() @@ -271,6 +443,7 @@ class TenantPool implements PoolStrategy { connectionString: settings.dbUrl, connectionTimeoutMillis: databaseConnectionTimeout, ssl: sslSettings ? { ...sslSettings } : undefined, + application_name: databaseApplicationName, }, acquireConnectionTimeout: databaseConnectionTimeout, }) diff --git a/src/internal/database/pubsub.ts b/src/internal/database/pubsub.ts index 810c4d116..904902119 100644 --- a/src/internal/database/pubsub.ts +++ b/src/internal/database/pubsub.ts @@ -1,6 +1,6 @@ -import { PostgresPubSub } from '../pubsub' import { getConfig } from '../../config' import { logger } from '../monitoring' +import { PostgresPubSub } from '../pubsub' const { isMultitenant, databaseURL, multitenantDatabaseUrl } = getConfig() @@ -8,8 +8,11 @@ const connectionString = isMultitenant ? (multitenantDatabaseUrl as string) : da export const PubSub = new PostgresPubSub(connectionString) PubSub.on('error', (err) => { - logger.error('PubSub error', { - type: 'pubsub', - error: err, - }) + logger.error( + { + type: 'pubsub', + error: err, + }, + 'PubSub error' + ) }) diff --git a/src/test/database/util.test.ts b/src/internal/database/ssl.test.ts similarity index 100% rename from src/test/database/util.test.ts rename to src/internal/database/ssl.test.ts diff --git a/src/internal/database/ssl.ts b/src/internal/database/ssl.ts index e884362ac..62cdf0c6a 100644 --- a/src/internal/database/ssl.ts +++ b/src/internal/database/ssl.ts @@ -1,6 +1,6 @@ import { logger } from '@internal/monitoring' -import { ConnectionOptions } from 'tls' import * as ipAddr from 'ip-address' +import { ConnectionOptions } from 'tls' export function getSslSettings({ connectionString, diff --git a/src/internal/database/tenant.ts b/src/internal/database/tenant.ts index 90d90f789..74f3dd36f 100644 --- a/src/internal/database/tenant.ts +++ b/src/internal/database/tenant.ts @@ -1,19 +1,25 @@ -import { getConfig, JwksConfig, JwksConfigKey, JwksConfigKeyOCT } from '../../config' -import { decrypt } from '../auth' -import { JWKSManager, JWKSManagerStoreKnex } from '../auth/jwks' -import { multitenantKnex } from './multitenant-db' -import { JWTPayload } from 'jose' -import { PubSubAdapter } from '../pubsub' -import { createMutexByKey } from '../concurrency' +import { + createLruCache, + DEFAULT_CACHE_PURGE_STALE_INTERVAL_MS, + TENANT_CONFIG_CACHE_NAME, +} from '@internal/cache' +import { TenantConnection } from '@internal/database/connection' +import { lastLocalMigrationName } from '@internal/database/migrations/files' import { ERRORS } from '@internal/errors' +import { logger, logSchema } from '@internal/monitoring' import { - S3CredentialsManagerStoreKnex, S3CredentialsManager, + S3CredentialsManagerStoreKnex, } from '@storage/protocols/s3/credentials' -import { TenantConnection } from '@internal/database/connection' -import { logger, logSchema } from '@internal/monitoring' +import { JWTPayload } from 'jose' +import objectSizeOf from 'object-sizeof' +import { getConfig, JwksConfig, JwksConfigKey, JwksConfigKeyOCT } from '../../config' +import { decrypt } from '../auth' +import { JWKSManager, JWKSManagerStoreKnex } from '../auth/jwks' +import { createMutexByKey } from '../concurrency' +import { PubSubAdapter } from '../pubsub' import { DBMigration } from './migrations/types' -import { lastLocalMigrationName } from '@internal/database/migrations/files' +import { multitenantKnex } from './multitenant-db' type DBPoolMode = 'single_use' | 'recycled' @@ -38,7 +44,18 @@ interface TenantConfig { disableEvents?: string[] } +type GetTenantConfigOptions = { + recordMetrics?: boolean +} + +type LegacyJwksConfig = NonNullable + export interface Features { + vectorBuckets: { + enabled: boolean + maxBuckets: number + maxIndexes: number + } imageTransformation: { enabled: boolean maxResolution?: number @@ -63,10 +80,28 @@ export enum TenantMigrationStatus { FAILED_STALE = 'FAILED_STALE', } -const { isMultitenant, dbServiceRole, serviceKeyAsync, jwtSecret, dbMigrationFreezeAt } = - getConfig() - -const tenantConfigCache = new Map() +const { + isMultitenant, + dbServiceRole, + dbMigrationFreezeAt, + icebergEnabled, + vectorEnabled, + multitenantDatabaseQueryTimeout, +} = getConfig() + +export const TENANT_CONFIG_CACHE_MAX_ITEMS = 16384 +export const TENANT_CONFIG_CACHE_MAX_SIZE_BYTES = 1024 * 1024 * 50 // 50 MiB +export const TENANT_CONFIG_CACHE_TTL_MS = 1000 * 60 * 60 // 1h + +const tenantConfigCache = createLruCache(TENANT_CONFIG_CACHE_NAME, { + max: TENANT_CONFIG_CACHE_MAX_ITEMS, + maxSize: TENANT_CONFIG_CACHE_MAX_SIZE_BYTES, + ttl: TENANT_CONFIG_CACHE_TTL_MS, + sizeCalculation: (value) => objectSizeOf(value), + updateAgeOnGet: true, + allowStale: false, + purgeStaleIntervalMs: DEFAULT_CACHE_PURGE_STALE_INTERVAL_MS, +}) const tenantMutex = createMutexByKey() @@ -76,19 +111,62 @@ export const s3CredentialsManager = new S3CredentialsManager( new S3CredentialsManagerStoreKnex(multitenantKnex) ) -const singleTenantServiceKey: - | { - jwt: Promise - payload: { role: string } & JWTPayload - } - | undefined = !isMultitenant - ? { - jwt: serviceKeyAsync, - payload: { - role: dbServiceRole, - }, - } - : undefined +// Cache merged legacy JWKS objects by the active + legacy config object identities +// so repeated reads reuse a stable merged object without mutating either input. +const mergedTenantJwksCache = new WeakMap>() + +function getSingleTenantJwtConfig(): { + secret: string + jwks: JwksConfig +} { + const { jwtSecret, jwtJWKS } = getConfig() + const jwks = (jwtJWKS || { keys: [] }) as JwksConfig + + return { + secret: jwtSecret, + jwks, + } +} + +async function getSingleTenantServiceKeyUser(): Promise<{ + jwt: string + payload: { role: string } & JWTPayload +}> { + const { serviceKeyAsync, dbServiceRole } = getConfig() + + return { + jwt: await serviceKeyAsync, + payload: { + role: dbServiceRole, + }, + } +} + +function mergeTenantJwksWithLegacyKeys( + tenantJwks: JwksConfig, + legacyJwks: LegacyJwksConfig +): JwksConfig { + let mergedByLegacyJwks = mergedTenantJwksCache.get(tenantJwks) + + if (!mergedByLegacyJwks) { + mergedByLegacyJwks = new WeakMap() + mergedTenantJwksCache.set(tenantJwks, mergedByLegacyJwks) + } + + const cachedMergedJwks = mergedByLegacyJwks.get(legacyJwks) + if (cachedMergedJwks) { + return cachedMergedJwks + } + + const mergedJwks: JwksConfig = { + ...tenantJwks, + keys: [...tenantJwks.keys, ...legacyJwks.keys], + } + + mergedByLegacyJwks.set(legacyJwks, mergedJwks) + + return mergedJwks +} /** * Deletes tenants config from the in-memory cache @@ -103,21 +181,33 @@ export function deleteTenantConfig(tenantId: string): void { * for quick subsequent access * @param tenantId */ -export async function getTenantConfig(tenantId: string): Promise { +export async function getTenantConfig( + tenantId: string, + options?: GetTenantConfigOptions +): Promise { if (!tenantId) { throw ERRORS.InvalidTenantId() } - if (tenantConfigCache.has(tenantId)) { - return tenantConfigCache.get(tenantId)! + const cachedConfig = tenantConfigCache.get(tenantId, { + recordMetrics: options?.recordMetrics, + }) + if (cachedConfig !== undefined) { + return cachedConfig } return tenantMutex(tenantId, async () => { - if (tenantConfigCache.has(tenantId)) { - return tenantConfigCache.get(tenantId)! + const cachedConfig = tenantConfigCache.get(tenantId, { recordMetrics: false }) + if (cachedConfig !== undefined) { + return cachedConfig } - const tenant = await multitenantKnex.table('tenants').first().where('id', tenantId) + const tenant = await multitenantKnex + .table('tenants') + .first() + .where('id', tenantId) + .abortOnSignal(AbortSignal.timeout(multitenantDatabaseQueryTimeout)) + if (!tenant) { throw ERRORS.MissingTenantConfig(tenantId) } @@ -136,6 +226,9 @@ export async function getTenantConfig(tenantId: string): Promise { feature_iceberg_catalog_max_catalogs, feature_iceberg_catalog_max_namespaces, feature_iceberg_catalog_max_tables, + feature_vector_buckets, + feature_vector_buckets_max_buckets, + feature_vector_buckets_max_indexes, image_transformation_max_resolution, database_pool_url, max_connections, @@ -154,9 +247,9 @@ export async function getTenantConfig(tenantId: string): Promise { databasePoolUrl: database_pool_url ? decrypt(database_pool_url) : undefined, databasePoolMode: database_pool_mode, fileSizeLimit: Number(file_size_limit), - jwtSecret: jwtSecret, + jwtSecret, jwks, - serviceKey: serviceKey, + serviceKey, serviceKeyPayload: { role: dbServiceRole }, maxConnections: max_connections ? Number(max_connections) : undefined, features: { @@ -171,11 +264,16 @@ export async function getTenantConfig(tenantId: string): Promise { enabled: feature_purge_cache, }, icebergCatalog: { - enabled: feature_iceberg_catalog, + enabled: icebergEnabled || feature_iceberg_catalog, maxNamespaces: feature_iceberg_catalog_max_namespaces, maxTables: feature_iceberg_catalog_max_tables, maxCatalogs: feature_iceberg_catalog_max_catalogs, }, + vectorBuckets: { + enabled: vectorEnabled || feature_vector_buckets, + maxBuckets: feature_vector_buckets_max_buckets, + maxIndexes: feature_vector_buckets_max_indexes, + }, }, migrationVersion: migrations_version, migrationStatus: migrations_status, @@ -185,7 +283,7 @@ export async function getTenantConfig(tenantId: string): Promise { } tenantConfigCache.set(tenantId, config) - return tenantConfigCache.get(tenantId)! + return config }) } @@ -199,10 +297,7 @@ export async function getServiceKeyUser(tenantId: string) { } } - return { - jwt: await singleTenantServiceKey!.jwt, - payload: singleTenantServiceKey!.payload, - } + return getSingleTenantServiceKeyUser() } /** @@ -269,22 +364,18 @@ export async function tenantHasFeature( * Get the jwt key from the tenant config * @param tenantId */ -export async function getJwtSecret( - tenantId: string -): Promise<{ secret: string; urlSigningKey: string | JwksConfigKeyOCT; jwks: JwksConfig }> { - const { jwtJWKS } = getConfig() - let secret = jwtSecret - let jwks = jwtJWKS || { keys: [] } +export async function getJwtSecret(tenantId: string): Promise<{ + secret: string + urlSigningKey: string | JwksConfigKeyOCT + jwks: JwksConfig +}> { + let { secret, jwks } = getSingleTenantJwtConfig() if (isMultitenant) { const config = await getTenantConfig(tenantId) const tenantJwks = await jwksManager.getJwksTenantConfig(tenantId) - if (config.jwks?.keys) { - // merge jwks from legacy jwks column if they exist - tenantJwks.keys = [...tenantJwks.keys, ...config.jwks.keys] - } secret = config.jwtSecret - jwks = tenantJwks + jwks = config.jwks?.keys ? mergeTenantJwksWithLegacyKeys(tenantJwks, config.jwks) : tenantJwks } const urlSigningKey = jwks.urlSigningKey || secret @@ -325,7 +416,7 @@ export async function listenForTenantUpdate(pubSub: PubSubAdapter): Promise new StorageBackendError({ - code: ErrorCode.InvalidRequest, + code: ErrorCode.ResourceNotEmpty, resource: bucket, httpStatusCode: 409, message: `The bucket you tried to delete is not empty`, @@ -61,27 +75,34 @@ export const ERRORS = { message: `The maximum number of this resource ${limit} is reached`, originalError: e, }), + IcebergResourceNotEmpty: (resource: string, name: string, e?: Error) => + new StorageBackendError({ + code: ErrorCode.IcebergResourceNotEmpty, + httpStatusCode: 400, + message: `The resource ${resource}: ${name} is not empty`, + originalError: e, + }), FeatureNotEnabled: (resource: string, feature: string, e?: Error) => new StorageBackendError({ - code: ErrorCode.InvalidRequest, - resource: resource, + code: ErrorCode.FeatureNotEnabled, + resource, httpStatusCode: 409, message: `The feature ${feature} is not enabled for this resource`, originalError: e, }), NotSupported: (feature: string, e?: Error) => new StorageBackendError({ - code: ErrorCode.InvalidRequest, + code: ErrorCode.NotSupported, httpStatusCode: 409, message: `The feature ${feature} is not enabled for this resource`, originalError: e, }), - UnableToEmptyBucket: (bucket: string) => + UnableToEmptyBucket: (bucket: string, msg: string) => new StorageBackendError({ code: ErrorCode.InvalidRequest, resource: bucket, httpStatusCode: 409, - message: `Unable to empty the bucket because it contains too many objects`, + message: msg, }), NoSuchBucket: (bucket: string, e?: Error) => new StorageBackendError({ @@ -120,7 +141,7 @@ export const ERRORS = { InvalidParameter: (parameter: string, opts?: { error?: Error; message?: string }) => new StorageBackendError({ - code: ErrorCode.MissingParameter, + code: ErrorCode.InvalidParameter, httpStatusCode: 400, message: opts?.message || `Invalid Parameter ${parameter}`, originalError: opts?.error, @@ -217,7 +238,7 @@ export const ERRORS = { new StorageBackendError({ code: ErrorCode.TusError, httpStatusCode: statusCode, - message: message, + message, }), MissingTenantConfig: (tenantId: string) => @@ -235,6 +256,22 @@ export const ERRORS = { message: `mime type ${mimeType} is not supported`, }), + InvalidXRobotsTag: (message: string) => + new StorageBackendError({ + error: 'invalid_x_robots_tag', + code: ErrorCode.InvalidRequest, + httpStatusCode: 400, + message: `Invalid X-Robots-Tag header: ${message}`, + }), + + InvalidHeaderChar: (headerName: string, headerValue: string) => + new StorageBackendError({ + error: 'invalid_header_char', + code: ErrorCode.InvalidRequest, + httpStatusCode: 400, + message: `Invalid character in response header "${headerName}": ${headerValue.substring(0, 50)}`, + }), + InvalidRange: () => new StorageBackendError({ error: 'invalid_range', @@ -243,12 +280,12 @@ export const ERRORS = { message: `invalid range provided`, }), - EntityTooLarge: (e?: Error, entity = 'object') => + EntityTooLarge: (e?: Error, entity = 'object', limit = 'the maximum allowed size') => new StorageBackendError({ error: 'Payload too large', code: ErrorCode.EntityTooLarge, httpStatusCode: 413, - message: `The ${entity} exceeded the maximum allowed size`, + message: `The ${entity} exceeded ${limit}`, originalError: e, }), @@ -264,7 +301,7 @@ export const ERRORS = { new StorageBackendError({ code: statusCode > 499 ? ErrorCode.InternalError : ErrorCode.InvalidRequest, httpStatusCode: statusCode, - message: message, + message, originalError: e, }), @@ -339,6 +376,39 @@ export const ERRORS = { originalError: e, }), + DatabaseConnectionLimit: (e?: Error) => + new StorageBackendError({ + code: ErrorCode.DatabaseConnectionLimit, + httpStatusCode: 503, + message: + 'The database has reached its maximum number of connections. Please try again later.', + originalError: e, + }), + + DatabaseReadOnly: (e?: Error) => + new StorageBackendError({ + code: ErrorCode.DatabaseReadOnly, + httpStatusCode: 503, + message: 'The database is currently in read-only mode. Please try again later.', + originalError: e, + }), + + InvalidObjectDefinition: (e?: Error) => + new StorageBackendError({ + code: ErrorCode.DatabaseInvalidObjectDefinition, + httpStatusCode: 503, + message: 'The database schema is invalid or incompatible.', + originalError: e, + }), + + DatabaseSchemaMismatch: (e?: Error) => + new StorageBackendError({ + code: ErrorCode.DatabaseSchemaMismatch, + httpStatusCode: 503, + message: 'The database schema is out of sync. Please run migrations or contact support.', + originalError: e, + }), + ResourceLocked: (e?: Error) => new StorageBackendError({ code: ErrorCode.ResourceLocked, @@ -355,11 +425,19 @@ export const ERRORS = { originalError: e, }), + TransactionError: (message: string, err?: Error) => + new StorageBackendError({ + code: ErrorCode.TransactionError, + httpStatusCode: 409, + message, + originalError: err, + }), + DatabaseError: (message: string, err?: Error) => new StorageBackendError({ code: ErrorCode.DatabaseError, httpStatusCode: 500, - message: message, + message, originalError: err, }), @@ -390,7 +468,7 @@ export const ERRORS = { new StorageBackendError({ code: ErrorCode.InvalidChecksum, httpStatusCode: 400, - message: message, + message, }), MissingPart: (partNumber: number, uploadId: string) => @@ -404,16 +482,16 @@ export const ERRORS = { new StorageBackendError({ code: ErrorCode.Aborted, httpStatusCode: 500, - message: message, + message, originalError, }), AbortedTerminate: (message: string, originalError?: unknown) => new StorageBackendError({ code: ErrorCode.AbortedTerminate, httpStatusCode: 500, - message: message, + message, originalError, - }), + }).withConnectionClose(), NoSuchCatalog: (name: string) => { return new StorageBackendError({ code: ErrorCode.NoSuchCatalog, @@ -421,16 +499,75 @@ export const ERRORS = { message: `Catalog name "${name}" not found`, }) }, + S3VectorConflictException(resource: string, name: string) { + return new StorageBackendError({ + code: ErrorCode.S3VectorConflictException, + httpStatusCode: 409, + message: `${resource} "${name}" already exists`, + }) + }, + S3VectorNotFoundException(resource: string, name: string) { + return new StorageBackendError({ + code: ErrorCode.S3VectorNotFoundException, + httpStatusCode: 404, + message: `resource "${name}" not found`, + }) + }, + S3VectorBucketNotEmpty(name: string) { + return new StorageBackendError({ + code: ErrorCode.S3VectorBucketNotEmpty, + httpStatusCode: 400, + message: `Vector Bucket "${name}" not empty`, + }) + }, + S3VectorMaxBucketsExceeded(maxBuckets: number) { + return new StorageBackendError({ + code: ErrorCode.S3VectorMaxBucketsExceeded, + httpStatusCode: 400, + message: `Maximum number of buckets exceeded. Max allowed is ${maxBuckets}. Contact support to increase your limit.`, + }) + }, + S3VectorMaxIndexesExceeded(maxIndexes: number) { + return new StorageBackendError({ + code: ErrorCode.S3VectorMaxIndexesExceeded, + httpStatusCode: 400, + message: `Maximum number of indexes exceeded. Max allowed is ${maxIndexes}. Contact support to increase your limit.`, + }) + }, + NoAvailableShard() { + return new StorageBackendError({ + code: ErrorCode.NoAvailableShard, + httpStatusCode: 500, + message: `No available shards are available to host the resource. Please try again later.`, + }) + }, + ShardNotFound(shardId: string) { + return new StorageBackendError({ + code: ErrorCode.ShardNotFound, + httpStatusCode: 404, + message: `Shard not found: ${shardId}`, + }) + }, } export function isStorageError(errorType: ErrorCode, error: any): error is StorageBackendError { return error instanceof StorageBackendError && error.code === errorType } +function hasStatusCode(error: Error): error is Error & { statusCode: number } { + return 'statusCode' in error && typeof (error as any).statusCode === 'number' +} + export function normalizeRawError(error: any) { if (error instanceof Error) { - const statusCode = - error instanceof StorageBackendError && error.httpStatusCode ? error.httpStatusCode : 0 + let statusCode = 0 + if (error instanceof StorageBackendError && error.httpStatusCode) { + statusCode = error.httpStatusCode + } else if (hasStatusCode(error)) { + // Fastify validation errors include statusCode we can use + statusCode = error.statusCode + } + return { raw: JSON.stringify(error), name: error.name, @@ -444,7 +581,7 @@ export function normalizeRawError(error: any) { return { raw: JSON.stringify(error), } - } catch (e) { + } catch { return { raw: 'Failed to stringify error', } diff --git a/src/internal/errors/index.ts b/src/internal/errors/index.ts index 8a6b8775c..6ae0ce1a5 100644 --- a/src/internal/errors/index.ts +++ b/src/internal/errors/index.ts @@ -1,3 +1,4 @@ -export * from './storage-error' export * from './codes' +export * from './render' export * from './renderable' +export * from './storage-error' diff --git a/src/internal/errors/render.test.ts b/src/internal/errors/render.test.ts new file mode 100644 index 000000000..245f571ca --- /dev/null +++ b/src/internal/errors/render.test.ts @@ -0,0 +1,40 @@ +import { ErrorCode } from './codes' +import { render } from './render' + +describe('render', () => { + it('preserves message details for plain errors', () => { + expect(render(new Error('Entity expansion limit exceeded'))).toEqual({ + statusCode: '500', + code: 'InternalError', + error: 'InternalError', + message: 'Entity expansion limit exceeded', + }) + }) + + it('uses renderable error payloads as-is', () => { + const error = { + render: () => ({ + statusCode: '499', + code: ErrorCode.AbortedTerminate, + error: ErrorCode.AbortedTerminate, + message: 'client disconnected', + }), + } + + expect(render(error)).toEqual({ + statusCode: '499', + code: ErrorCode.AbortedTerminate, + error: ErrorCode.AbortedTerminate, + message: 'client disconnected', + }) + }) + + it('ignores non-callable render properties', () => { + expect(render({ render: 'not-a-function' })).toEqual({ + statusCode: '500', + code: 'InternalError', + error: 'InternalError', + message: 'Internal server error', + }) + }) +}) diff --git a/src/internal/errors/render.ts b/src/internal/errors/render.ts new file mode 100644 index 000000000..ec6c950a0 --- /dev/null +++ b/src/internal/errors/render.ts @@ -0,0 +1,9 @@ +import { isRenderableError, StorageBackendError } from './storage-error' + +export function render(error: unknown) { + if (isRenderableError(error)) { + return error.render() + } + + return StorageBackendError.fromError(error).render() +} diff --git a/src/internal/errors/storage-error.ts b/src/internal/errors/storage-error.ts index f0b727434..53d80b28d 100644 --- a/src/internal/errors/storage-error.ts +++ b/src/internal/errors/storage-error.ts @@ -1,6 +1,8 @@ +import type { S3ServiceException } from '@aws-sdk/client-s3' import { ErrorCode } from './codes' import { RenderableError, StorageErrorOptions } from './renderable' -import { S3ServiceException } from '@aws-sdk/client-s3' + +const CLOSE_CONNECTION_METADATA_KEY = 'closeConnection' /** * A generic error that should be always thrown for generic exceptions @@ -57,7 +59,7 @@ export class StorageBackendError extends Error implements RenderableError { return new StorageBackendError({ error: oldErrorMessage, - code: code, + code, httpStatusCode, message, originalError: error, @@ -74,6 +76,17 @@ export class StorageBackendError extends Error implements RenderableError { return this } + withConnectionClose() { + return this.withMetadata({ + ...this.metadata, + [CLOSE_CONNECTION_METADATA_KEY]: true, + }) + } + + shouldCloseConnection() { + return Boolean(this.metadata?.[CLOSE_CONNECTION_METADATA_KEY]) + } + render() { return { statusCode: this.httpStatusCode.toString(), @@ -93,7 +106,9 @@ export class StorageBackendError extends Error implements RenderableError { * @param error */ export function isRenderableError(error: unknown): error is RenderableError { - return !!error && typeof error === 'object' && 'render' in error + return ( + !!error && typeof error === 'object' && 'render' in error && typeof error.render === 'function' + ) } /** diff --git a/src/internal/fs.ts b/src/internal/fs.ts new file mode 100644 index 000000000..318366ac5 --- /dev/null +++ b/src/internal/fs.ts @@ -0,0 +1,27 @@ +import * as fs from 'node:fs/promises' +import path from 'node:path' + +export async function ensureDir(dirPath: string): Promise { + await fs.mkdir(dirPath, { recursive: true }) +} + +export async function ensureFile(filePath: string): Promise { + await ensureDir(path.dirname(filePath)) + + // Open in append mode so missing files are created without truncating existing ones. + const handle = await fs.open(filePath, 'a') + await handle.close() +} + +export async function pathExists(filePath: string): Promise { + try { + await fs.access(filePath) + return true + } catch { + return false + } +} + +export async function removePath(filePath: string): Promise { + await fs.rm(filePath, { recursive: true, force: true }) +} diff --git a/src/internal/hashing/index.ts b/src/internal/hashing/index.ts new file mode 100644 index 000000000..df225df91 --- /dev/null +++ b/src/internal/hashing/index.ts @@ -0,0 +1 @@ +export * from './string-to-int' diff --git a/src/internal/hashing/string-to-int.ts b/src/internal/hashing/string-to-int.ts new file mode 100644 index 000000000..0105e3b56 --- /dev/null +++ b/src/internal/hashing/string-to-int.ts @@ -0,0 +1,9 @@ +export function hashStringToInt(str: string): number { + let hash = 5381 + let i = -1 + while (i < str.length - 1) { + i += 1 + hash = (hash * 33) ^ str.charCodeAt(i) + } + return hash >>> 0 +} diff --git a/src/internal/http/agent.ts b/src/internal/http/agent.ts index cf7be6581..9f8e6a018 100644 --- a/src/internal/http/agent.ts +++ b/src/internal/http/agent.ts @@ -1,10 +1,10 @@ -import Agent, { HttpsAgent } from 'agentkeepalive' import { - HttpPoolErrorGauge, - HttpPoolFreeSocketsGauge, - HttpPoolPendingRequestsGauge, - HttpPoolSocketsGauge, + httpPoolBusySockets, + httpPoolErrors, + httpPoolFreeSockets, + httpPoolPendingRequests, } from '@internal/monitoring/metrics' +import Agent, { HttpsAgent } from 'agentkeepalive' import { getConfig } from '../../config' const { region } = getConfig() @@ -27,28 +27,14 @@ export interface AgentStats { /** * Creates an instrumented agent - * Adding prometheus metrics to the agent - * Optimized for COG/range request workloads with: - * - Longer socket reuse timeout (60s instead of 15s) for burst patterns - * - Faster keep-alive probes (100ms) to detect stale connections - * - FIFO scheduling for better handling of concurrent burst requests - * - Increased free socket pool to reduce connection overhead + * Adding metrics to the agent */ export function createAgent(name: string, options: { maxSockets: number }): InstrumentedAgent { const agentOptions = { maxSockets: options.maxSockets, keepAlive: true, - // Faster keep-alive probes to quickly reuse connections during burst COG tile requests - keepAliveMsecs: 200, - // Hold sockets longer (60s) to handle COG workloads where GDAL makes many requests in bursts - // then pauses, avoiding constant reconnection overhead - freeSocketTimeout: 1000 * 60, - // Increase free socket pool to avoid connection churn during concurrent range requests - maxFreeSockets: Math.floor(options.maxSockets * 0.25), - // Use FIFO scheduling for better handling of burst request patterns (vs LIFO default) - scheduling: 'fifo' as const, - // Timeout for socket creation (prevents hanging on connection issues) - timeout: 30000, + keepAliveMsecs: 1000, + freeSocketTimeout: 1000 * 15, } const httpAgent = new Agent(agentOptions) @@ -72,31 +58,17 @@ export function createAgent(name: string, options: { maxSockets: number }): Inst } /** - * Metrics - * - * HttpPoolSockets - * HttpPoolFreeSockets - * HttpPoolPendingRequests - * HttpPoolError - * - * @param name - * @param protocol - * @param stats + * Updates HTTP agent metrics */ function updateHttpAgentMetrics(name: string, protocol: string, stats: AgentStats) { - // Update the metrics with calculated values - HttpPoolSocketsGauge.set({ name, region, protocol }, stats.busySocketCount) - HttpPoolFreeSocketsGauge.set({ name, region, protocol }, stats.freeSocketCount) - HttpPoolPendingRequestsGauge.set({ name, region }, stats.pendingRequestCount) - HttpPoolErrorGauge.set({ name, region, type: 'socket_error', protocol }, stats.errorSocketCount) - HttpPoolErrorGauge.set( - { name, region, type: 'timeout_socket_error', protocol }, - stats.timeoutSocketCount - ) - HttpPoolErrorGauge.set( - { name, region, type: 'create_socket_error', protocol }, - stats.createSocketErrorCount - ) + const baseAttrs = { name, protocol } + + httpPoolBusySockets.record(stats.busySocketCount, baseAttrs) + httpPoolFreeSockets.record(stats.freeSocketCount, baseAttrs) + httpPoolPendingRequests.record(stats.pendingRequestCount, { name, region }) + httpPoolErrors.record(stats.errorSocketCount, { ...baseAttrs, type: 'socket_error' }) + httpPoolErrors.record(stats.timeoutSocketCount, { ...baseAttrs, type: 'timeout_socket_error' }) + httpPoolErrors.record(stats.createSocketErrorCount, { ...baseAttrs, type: 'create_socket_error' }) } export function watchAgent(name: string, protocol: 'http' | 'https', agent: Agent | HttpsAgent) { @@ -109,7 +81,7 @@ export function watchAgent(name: string, protocol: 'http' | 'https', agent: Agen }, 5000) } -// Function to update Prometheus metrics based on the current status of the agent +// Function to update metrics based on the current status of the agent export function gatherHttpAgentStats(status: Agent.AgentStatus) { // Calculate the number of busy sockets by iterating over the `sockets` object let busySocketCount = 0 diff --git a/src/internal/monitoring/event-loop.ts b/src/internal/monitoring/event-loop.ts deleted file mode 100644 index 7d1ac220a..000000000 --- a/src/internal/monitoring/event-loop.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { createHook } from 'node:async_hooks' -import { trace } from '@opentelemetry/api' - -const THRESHOLD_NS = 1e8 // 100ms - -const cache = new Map() - -function init(asyncId: number, type: string, triggerAsyncId: number, resource: any) { - cache.set(asyncId, { - type, - }) -} - -function destroy(asyncId: number) { - cache.delete(asyncId) -} - -function before(asyncId: number) { - const cached = cache.get(asyncId) - - if (!cached) { - return - } - - cache.set(asyncId, { - ...cached, - start: process.hrtime(), - }) -} - -function after(asyncId: number) { - const cached = cache.get(asyncId) - - if (!cached) { - return - } - - cache.delete(asyncId) - - if (!cached.start) { - return - } - - const diff = process.hrtime(cached.start) - const diffNs = diff[0] * 1e9 + diff[1] - - if (diffNs > THRESHOLD_NS) { - const time = diffNs / 1e6 // in ms - - const tracer = trace.getTracer('event-loop-monitor') - const activeSpan = trace.getActiveSpan() - - const newSpan = tracer.startSpan('event-loop-blocked', { - startTime: new Date(new Date().getTime() - time), - attributes: { - asyncType: cached.type, - label: 'EventLoopMonitor', - }, - links: activeSpan ? [{ context: activeSpan.spanContext() }] : undefined, - }) - - newSpan.end() - } -} - -export const eventLoopMonitor = () => { - const hook = createHook({ init, before, after, destroy }) - - return { - enable: () => { - console.log('🥸 Initializing event loop monitor') - - hook.enable() - }, - disable: () => { - console.log('🥸 Disabling event loop monitor') - - hook.disable() - }, - } -} diff --git a/src/internal/monitoring/logger.test.ts b/src/internal/monitoring/logger.test.ts new file mode 100644 index 000000000..4780c3826 --- /dev/null +++ b/src/internal/monitoring/logger.test.ts @@ -0,0 +1,70 @@ +import { vi } from 'vitest' + +const mockedLoggerModules = ['pino', '../../config'] as const + +describe('logger serializers', () => { + afterEach(() => { + for (const moduleId of mockedLoggerModules) { + vi.doUnmock(moduleId) + } + + vi.restoreAllMocks() + vi.resetModules() + }) + + test('res serializer tolerates synthetic replies without getHeaders', async () => { + let resSerializer: + | ((reply: { statusCode: number; getHeaders?: () => Record }) => unknown) + | undefined + + const loggerStub = { + child: vi.fn(), + debug: vi.fn(), + error: vi.fn(), + fatal: vi.fn(), + flush: vi.fn(), + info: vi.fn(), + level: 'info', + silent: vi.fn(), + trace: vi.fn(), + warn: vi.fn(), + } + loggerStub.child.mockReturnValue(loggerStub) + + const pinoMock = Object.assign( + vi.fn((options: { serializers: { res: typeof resSerializer } }) => { + resSerializer = options.serializers.res + return loggerStub + }), + { + stdTimeFunctions: { + isoTime: vi.fn(), + }, + } + ) + + vi.doMock('pino', () => ({ + default: pinoMock, + Logger: class Logger {}, + })) + vi.doMock('../../config', () => ({ + getConfig: vi.fn(() => ({ + logLevel: 'info', + logflareApiKey: undefined, + logflareBatchSize: 1, + logflareEnabled: false, + logflareSourceToken: undefined, + region: 'local', + })), + })) + + await import('./logger') + + expect(resSerializer).toBeDefined() + expect(() => resSerializer?.({ statusCode: 503 })).not.toThrow() + expect(resSerializer?.({ statusCode: 503 })).toEqual({ + headers: {}, + statusCode: 503, + }) + }) +}) diff --git a/src/internal/monitoring/logger.ts b/src/internal/monitoring/logger.ts index eacff4888..2ae539602 100644 --- a/src/internal/monitoring/logger.ts +++ b/src/internal/monitoring/logger.ts @@ -1,9 +1,9 @@ -import pino, { BaseLogger } from 'pino' -import { getConfig } from '../../config' -import { FastifyReply, FastifyRequest } from 'fastify' +import { resolve } from 'node:path' import { URL } from 'node:url' import { normalizeRawError } from '@internal/errors' -import { resolve } from 'node:path' +import type { FastifyBaseLogger, FastifyReply, FastifyRequest } from 'fastify' +import pino, { Logger } from 'pino' +import { getConfig } from '../../config' const { logLevel, @@ -20,11 +20,23 @@ export const baseLogger = pino({ error(error) { return normalizeRawError(error) }, - res(reply) { - const getHeaders = typeof reply?.getHeaders === 'function' ? reply.getHeaders : () => ({}) + res(reply: Pick & Partial>) { + const headers = typeof reply.getHeaders === 'function' ? reply.getHeaders() : {} + return { - statusCode: reply?.statusCode, - headers: whitelistHeaders(getHeaders()), + statusCode: reply.statusCode, + headers: whitelistHeaders(headers), + } + }, + reqMetadata(metadata?: Record) { + if (!metadata) { + return undefined + } + + try { + return JSON.stringify(metadata) + } catch { + // no-op } }, req(request) { @@ -49,13 +61,19 @@ export const baseLogger = pino({ timestamp: pino.stdTimeFunctions.isoTime, }) -export const logger = baseLogger.child({ region }) +export let logger = baseLogger.child({ region }) + +export function setLogger(newLogger: Logger) { + logger = newLogger +} export interface RequestLog { type: 'request' req: FastifyRequest res?: FastifyReply + reqMetadata?: Record responseTime: number + executionTime?: number error?: Error | unknown role?: string owner?: string @@ -90,10 +108,10 @@ interface InfoLog { } export const logSchema = { - info: (logger: BaseLogger, message: string, log: InfoLog) => logger.info(log, message), - warning: (logger: BaseLogger, message: string, log: InfoLog | ErrorLog) => + info: (logger: FastifyBaseLogger, message: string, log: InfoLog) => logger.info(log, message), + warning: (logger: FastifyBaseLogger, message: string, log: InfoLog | ErrorLog) => logger.warn(log, message), - request: (logger: BaseLogger, message: string, log: RequestLog) => { + request: (logger: FastifyBaseLogger, message: string, log: RequestLog) => { if (!log.res) { logger.warn(log, message) return @@ -105,8 +123,8 @@ export const logSchema = { const logLevel = is4xxResponse ? 'warn' : is5xxResponse ? 'error' : 'info' logger[logLevel](log, message) }, - error: (logger: BaseLogger, message: string, log: ErrorLog) => logger.error(log, message), - event: (logger: BaseLogger, message: string, log: EventLog) => logger.info(log, message), + error: (logger: FastifyBaseLogger, message: string, log: ErrorLog) => logger.error(log, message), + event: (logger: FastifyBaseLogger, message: string, log: EventLog) => logger.info(log, message), } export function buildTransport(): pino.TransportMultiOptions { @@ -149,70 +167,74 @@ export function buildTransport(): pino.TransportMultiOptions { } } +const allowlistedHeaders = new Set([ + 'accept', + 'cf-connecting-ip', + 'cf-ipcountry', + 'host', + 'user-agent', + 'x-forwarded-proto', + 'x-forwarded-host', + 'x-forwarded-port', + 'x-forwarded-prefix', + 'referer', + 'content-length', + 'x-real-ip', + 'x-client-info', + 'x-forwarded-user-agent', + 'x-client-trace-id', + 'x-upsert', + 'content-type', + 'if-none-match', + 'if-modified-since', + 'upload-metadata', + 'upload-length', + 'upload-offset', + 'tus-resumable', + 'range', + 'cf-cache-status', + 'cf-ray', + 'location', + 'cache-control', + 'content-location', + 'content-range', + 'date', + 'transfer-encoding', + 'x-kong-proxy-latency', + 'x-kong-upstream-latency', + 'sb-gateway-mode', + 'sb-gateway-version', + 'x-transformations', + 'expires', + 'etag', + 'content-disposition', + 'last-modified', +]) + const whitelistHeaders = (headers: Record) => { const responseMetadata: Record = {} - const allowlistedRequestHeaders = [ - 'accept', - 'cf-connecting-ip', - 'cf-ipcountry', - 'host', - 'user-agent', - 'x-forwarded-proto', - 'x-forwarded-host', - 'x-forwarded-port', - 'x-forwarded-prefix', - 'referer', - 'content-length', - 'x-real-ip', - 'x-client-info', - 'x-forwarded-user-agent', - 'x-client-trace-id', - 'x-upsert', - 'content-type', - 'if-none-match', - 'if-modified-since', - 'upload-metadata', - 'upload-length', - 'upload-offset', - 'tus-resumable', - 'range', - ] - const allowlistedResponseHeaders = [ - 'cf-cache-status', - 'cf-ray', - 'location', - 'cache-control', - 'content-location', - 'content-range', - 'content-type', - 'content-length', - 'date', - 'transfer-encoding', - 'x-kong-proxy-latency', - 'x-kong-upstream-latency', - 'sb-gateway-mode', - 'sb-gateway-version', - 'x-transformations', - 'expires', - 'etag', - 'content-disposition', - 'last-modified', - ] - Object.keys(headers) - .filter( - (header) => - allowlistedRequestHeaders.includes(header) || allowlistedResponseHeaders.includes(header) - ) - .forEach((header) => { - responseMetadata[header.replace(/-/g, '_')] = `${headers[header]}` - }) + + for (const header in headers) { + if (allowlistedHeaders.has(header)) { + responseMetadata[header.replaceAll('-', '_')] = `${headers[header]}` + } + } return responseMetadata } export function redactQueryParamFromRequest(req: FastifyRequest, params: string[]) { - const lUrl = new URL(req.url, `${req.protocol}://${req.hostname}`) + const url = req.url + const qIdx = url.indexOf('?') + + // Fast path: no query string, nothing to redact + if (qIdx === -1) return url + + const query = url.slice(qIdx + 1) + // Fast path: no sensitive params present + if (!params.some((p) => query.includes(p))) return url + const lUrl = new URL(url, `${req.protocol}://${req.hostname}`) params.forEach((param) => { if (lUrl.searchParams.has(param)) { lUrl.searchParams.set(param, 'redacted') diff --git a/src/internal/monitoring/metrics.ts b/src/internal/monitoring/metrics.ts index 3b1423ad8..15ce368fc 100644 --- a/src/internal/monitoring/metrics.ts +++ b/src/internal/monitoring/metrics.ts @@ -1,126 +1,268 @@ -import client from 'prom-client' - -const Registry = client.Registry -export const MetricsRegistrar = new Registry() - -export const FileUploadStarted = new client.Gauge({ - name: 'storage_api_upload_started', - help: 'Upload started', - labelNames: ['region', 'is_multipart'], -}) - -export const FileUploadedSuccess = new client.Gauge({ - name: 'storage_api_upload_success', - help: 'Successful uploads', - labelNames: ['region', 'is_multipart', 'is_resumable', 'is_standard', 'is_s3'], -}) - -export const DbQueryPerformance = new client.Histogram({ - name: 'storage_api_database_query_performance', - help: 'Database query performance', - labelNames: ['region', 'name'], -}) - -export const QueueJobSchedulingTime = new client.Histogram({ - name: 'storage_api_queue_job_scheduled_time', - help: 'Time taken to schedule a job in the queue', - labelNames: ['region', 'name'], -}) - -export const QueueJobScheduled = new client.Gauge({ - name: 'storage_api_queue_job_scheduled', - help: 'Current number of pending messages in the queue', - labelNames: ['region', 'name'], -}) - -export const QueueJobCompleted = new client.Gauge({ - name: 'storage_api_queue_job_completed', - help: 'Current number of processed messages in the queue', - labelNames: ['region', 'name'], -}) - -export const QueueJobRetryFailed = new client.Gauge({ - name: 'storage_api_queue_job_retry_failed', - help: 'Current number of failed attempts messages in the queue', - labelNames: ['region', 'name'], -}) - -export const QueueJobError = new client.Gauge({ - name: 'storage_api_queue_job_error', - help: 'Current number of errored messages in the queue', - labelNames: ['region', 'name'], -}) - -export const S3UploadPart = new client.Histogram({ - name: 'storage_api_s3_upload_part', - help: 'S3 upload part performance', - labelNames: ['region'], -}) - -export const DbActivePool = new client.Gauge({ - name: 'storage_api_db_pool', - help: 'Number of database pools created', - labelNames: ['region'], -}) - -export const DbActiveConnection = new client.Gauge({ - name: 'storage_api_db_connections', - help: 'Number of database connections', - labelNames: ['region', 'is_external'], -}) - -// Create Prometheus metrics -export const HttpPoolSocketsGauge = new client.Gauge({ - name: 'storage_api_http_pool_busy_sockets', - help: 'Number of busy sockets currently in use', - labelNames: ['name', 'region', 'protocol'], -}) - -export const HttpPoolFreeSocketsGauge = new client.Gauge({ - name: 'storage_api_http_pool_free_sockets', - help: 'Number of free sockets available for reuse', - labelNames: ['name', 'region', 'protocol'], -}) - -export const HttpPoolPendingRequestsGauge = new client.Gauge({ - name: 'storage_api_http_pool_requests', - help: 'Number of pending requests waiting for a socket', - labelNames: ['name', 'region', 'protocol'], -}) - -export const HttpPoolErrorGauge = new client.Gauge({ - name: 'storage_api_http_pool_errors', - help: 'Number of pending requests waiting for a socket', - labelNames: ['name', 'region', 'type', 'protocol'], -}) - -// Object metadata cache metrics (for COG/geospatial workloads) -export const ObjectCacheSizeGauge = new client.Gauge({ - name: 'storage_object_metadata_cache_size', - help: 'Number of objects currently cached in memory', - labelNames: ['region'], -}) - -export const ObjectCacheHitsCounter = new client.Counter({ - name: 'storage_object_metadata_cache_hits_total', - help: 'Total number of cache hits (avoided DB queries)', - labelNames: ['region'], -}) - -export const ObjectCacheMissesCounter = new client.Counter({ - name: 'storage_object_metadata_cache_misses_total', - help: 'Total number of cache misses (required DB queries)', - labelNames: ['region'], -}) - -export const ObjectCacheEvictionsCounter = new client.Counter({ - name: 'storage_object_metadata_cache_evictions_total', - help: 'Total number of cache evictions (LRU or size-based)', - labelNames: ['region'], -}) - -export const ObjectCacheHitRateGauge = new client.Gauge({ - name: 'storage_object_metadata_cache_hit_rate', - help: 'Cache hit rate (hits / total requests)', - labelNames: ['region'], -}) +import { Attributes, metrics } from '@opentelemetry/api' +import { getConfig } from '../../config' + +const { prometheusMetricsIncludeTenantId } = getConfig() + +// ============================================================================ +// Metric Registry — tracks all metrics for admin API +// ============================================================================ +export type MetricType = 'histogram' | 'counter' | 'gauge' | 'updowncounter' + +export interface MetricRegistryEntry { + name: string + type: MetricType + enabled: boolean +} + +const metricsRegistry = new Map() + +const disabledMetrics = new Set( + (process.env.METRICS_DISABLED || '') + .split(',') + .map((s) => s.trim()) + .filter(Boolean) +) + +/** Returns all registered metrics with their status */ +export function getMetricsConfig(): MetricRegistryEntry[] { + return Array.from(metricsRegistry.values()) +} + +/** Enable or disable specific metrics by OTel instrument name */ +export function setMetricsEnabled(changes: { name: string; enabled: boolean }[]): void { + for (const { name, enabled } of changes) { + const entry = metricsRegistry.get(name) + if (entry) { + entry.enabled = enabled + } + } +} + +/** Check if a metric is enabled (for observable gauges that emit via callbacks) */ +export function isMetricEnabled(name: string): boolean { + return metricsRegistry.get(name)?.enabled !== false +} + +// ============================================================================ +// Meter & registration +// ============================================================================ +export const meter = metrics.getMeter('storage-api') + +function stripTenantAttrs(attrs: Attributes): Attributes { + const { tenantId, tenant_id, ...rest } = attrs as Record + return rest as Attributes +} + +/** + * Registers a metric in the admin registry and wraps .record()/.add() + * to automatically strip tenant attributes when prometheusMetricsIncludeTenantId is false. + */ +export function registerMetric(name: string, type: MetricType, factory: () => T): T { + metricsRegistry.set(name, { name, type, enabled: !disabledMetrics.has(name) }) + const instrument = factory() + + if (prometheusMetricsIncludeTenantId) return instrument + + // biome-ignore lint/suspicious/noExplicitAny: wrapping OTel instrument methods + const inst = instrument as any + if (typeof inst.record === 'function') { + const original = inst.record.bind(inst) + inst.record = (value: number, attrs?: Attributes) => + original(value, attrs ? stripTenantAttrs(attrs) : attrs) + } + if (typeof inst.add === 'function') { + const original = inst.add.bind(inst) + inst.add = (value: number, attrs?: Attributes) => + original(value, attrs ? stripTenantAttrs(attrs) : attrs) + } + + return instrument +} + +// ============================================================================ +// HTTP Request Metrics +// ============================================================================ +export const httpRequestDuration = registerMetric( + 'http_request_duration_seconds', + 'histogram', + () => + meter.createHistogram('http_request_duration_seconds', { + description: 'HTTP request duration in seconds', + unit: 's', + }) +) + +export const httpRequestSizeBytes = registerMetric('http_request_size_bytes', 'counter', () => + meter.createCounter('http_request_size_bytes', { + description: 'Total bytes received in HTTP requests (from content-length header)', + unit: 'bytes', + }) +) + +export const httpResponseSizeBytes = registerMetric('http_response_size_bytes', 'counter', () => + meter.createCounter('http_response_size_bytes', { + description: 'Total bytes sent in HTTP responses (from content-length header)', + unit: 'bytes', + }) +) + +// ============================================================================ +// Upload Metrics +// ============================================================================ +export const fileUploadStarted = registerMetric('upload_started', 'counter', () => + meter.createCounter('upload_started', { + description: 'Total uploads started', + }) +) + +export const fileUploadedSuccess = registerMetric('upload_success', 'counter', () => + meter.createCounter('upload_success', { + description: 'Total successful uploads', + }) +) + +// ============================================================================ +// Cache Metrics +// ============================================================================ +export const cacheRequestsTotal = registerMetric('cache_requests_total', 'counter', () => + meter.createCounter('cache_requests_total', { + description: 'Total cache lookups by cache and outcome', + }) +) + +export const cacheEvictionsTotal = registerMetric('cache_evictions_total', 'counter', () => + meter.createCounter('cache_evictions_total', { + description: 'Total cache evictions', + }) +) + +export const cacheEntries = registerMetric('cache_entries', 'gauge', () => + meter.createObservableGauge('cache_entries', { + description: 'Current number of entries stored in each cache', + }) +) + +export const cacheSizeBytes = registerMetric('cache_size_bytes', 'gauge', () => + meter.createObservableGauge('cache_size_bytes', { + description: 'Current estimated size of each cache in bytes', + unit: 'bytes', + }) +) + +// ============================================================================ +// Database Metrics +// ============================================================================ +export const dbQueryPerformance = registerMetric( + 'database_query_performance_seconds', + 'histogram', + () => + meter.createHistogram('database_query_performance_seconds', { + description: 'Database query performance in seconds', + unit: 's', + }) +) + +export const dbConnectionAcquireTime = registerMetric( + 'db_connection_acquire_seconds', + 'histogram', + () => + meter.createHistogram('db_connection_acquire_seconds', { + description: 'Time taken to acquire a database connection from the pool in seconds', + unit: 's', + }) +) + +// ============================================================================ +// Queue Metrics +// ============================================================================ +export const queueJobSchedulingTime = registerMetric( + 'queue_job_scheduled_time_seconds', + 'histogram', + () => + meter.createHistogram('queue_job_scheduled_time_seconds', { + description: 'Time taken to schedule a job in the queue in seconds', + unit: 's', + }) +) + +export const queueJobScheduled = registerMetric('queue_job_scheduled', 'updowncounter', () => + meter.createUpDownCounter('queue_job_scheduled', { + description: 'Current number of pending messages in the queue', + }) +) + +export const queueJobCompleted = registerMetric('queue_job_completed', 'updowncounter', () => + meter.createUpDownCounter('queue_job_completed', { + description: 'Current number of processed messages in the queue', + }) +) + +export const queueJobRetryFailed = registerMetric('queue_job_retry_failed', 'updowncounter', () => + meter.createUpDownCounter('queue_job_retry_failed', { + description: 'Current number of failed attempts messages in the queue', + }) +) + +export const queueJobError = registerMetric('queue_job_error', 'updowncounter', () => + meter.createUpDownCounter('queue_job_error', { + description: 'Current number of errored messages in the queue', + }) +) + +// ============================================================================ +// S3 Metrics +// ============================================================================ +export const s3UploadPart = registerMetric('s3_upload_part_seconds', 'histogram', () => + meter.createHistogram('s3_upload_part_seconds', { + description: 'S3 upload part performance in seconds', + unit: 's', + }) +) + +// ============================================================================ +// HTTP Pool Metrics +// ============================================================================ +export const httpPoolBusySockets = registerMetric('http_pool_busy_sockets', 'gauge', () => + meter.createGauge('http_pool_busy_sockets', { + description: 'Number of busy sockets currently in use', + }) +) + +export const httpPoolFreeSockets = registerMetric('http_pool_free_sockets', 'gauge', () => + meter.createGauge('http_pool_free_sockets', { + description: 'Number of free sockets available for reuse', + }) +) + +export const httpPoolPendingRequests = registerMetric('http_pool_requests', 'gauge', () => + meter.createGauge('http_pool_requests', { + description: 'Number of pending requests waiting for a socket', + }) +) + +export const httpPoolErrors = registerMetric('http_pool_errors', 'gauge', () => + meter.createGauge('http_pool_errors', { + description: 'Number of socket errors', + }) +) + +// ============================================================================ +// Database Pool Metrics (observable — collected only at export time) +// ============================================================================ +export const dbActivePool = registerMetric('db_active_local_pools', 'gauge', () => + meter.createObservableGauge('db_active_local_pools', { + description: 'Number of database pools created', + }) +) + +export const dbActiveConnection = registerMetric('db_connections', 'gauge', () => + meter.createObservableGauge('db_connections', { + description: 'Number of database connections in the pool', + }) +) + +export const dbInUseConnection = registerMetric('db_connections_in_use', 'gauge', () => + meter.createObservableGauge('db_connections_in_use', { + description: 'Number of database connections currently in use', + }) +) diff --git a/src/internal/monitoring/metrics/cache.ts b/src/internal/monitoring/metrics/cache.ts deleted file mode 100644 index e554aac7c..000000000 --- a/src/internal/monitoring/metrics/cache.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Gauge, Counter } from 'prom-client' - -/** - * Prometheus metrics for object metadata cache - * Used to monitor cache effectiveness for COG/geospatial workloads - */ - -export const ObjectCacheSizeGauge = new Gauge({ - name: 'storage_object_metadata_cache_size', - help: 'Number of objects currently cached in memory', - labelNames: ['region'], -}) - -export const ObjectCacheHitsCounter = new Counter({ - name: 'storage_object_metadata_cache_hits_total', - help: 'Total number of cache hits (avoided DB queries)', - labelNames: ['region'], -}) - -export const ObjectCacheMissesCounter = new Counter({ - name: 'storage_object_metadata_cache_misses_total', - help: 'Total number of cache misses (required DB queries)', - labelNames: ['region'], -}) - -export const ObjectCacheEvictionsCounter = new Counter({ - name: 'storage_object_metadata_cache_evictions_total', - help: 'Total number of cache evictions (LRU or size-based)', - labelNames: ['region'], -}) - -export const ObjectCacheHitRateGauge = new Gauge({ - name: 'storage_object_metadata_cache_hit_rate', - help: 'Cache hit rate (hits / total requests)', - labelNames: ['region'], -}) diff --git a/src/internal/monitoring/otel-class-instrumentations.ts b/src/internal/monitoring/otel-class-instrumentations.ts new file mode 100644 index 000000000..c27c01b3a --- /dev/null +++ b/src/internal/monitoring/otel-class-instrumentations.ts @@ -0,0 +1,199 @@ +import { S3Client } from '@aws-sdk/client-s3' +import { Upload } from '@aws-sdk/lib-storage' +import { TenantConnection } from '@internal/database' +import { Event as QueueBaseEvent } from '@internal/queue' +import { Permit, Semaphore } from '@shopify/semaphore' +import { S3Backend } from '@storage/backend' +import { StorageKnexDB } from '@storage/database' +import { ObjectStorage } from '@storage/object' +import { PgLock } from '@storage/protocols/tus' +import { Storage } from '@storage/storage' +import { Uploader } from '@storage/uploader' +import { S3Store } from '@tus/s3-store' +import { StreamSplitter } from '@tus/server' +import { ClassInstrumentation } from './otel-instrumentation' + +export const classInstrumentations = [ + new ClassInstrumentation({ + targetClass: Storage, + enabled: true, + methodsToInstrument: [ + 'findBucket', + 'listBuckets', + 'createBucket', + 'updateBucket', + 'countObjects', + 'deleteBucket', + 'emptyBucket', + 'healthcheck', + ], + }), + new ClassInstrumentation({ + targetClass: ObjectStorage, + enabled: true, + methodsToInstrument: [ + 'uploadNewObject', + 'uploadOverridingObject', + 'deleteObject', + 'deleteObjects', + 'updateObjectMetadata', + 'updateObjectOwner', + 'findObject', + 'findObjects', + 'copyObject', + 'moveObject', + 'searchObjects', + 'listObjectsV2', + 'signObjectUrl', + 'signObjectUrls', + 'signUploadObjectUrl', + 'verifyObjectSignature', + ], + }), + new ClassInstrumentation({ + targetClass: Uploader, + enabled: true, + methodsToInstrument: ['canUpload', 'prepareUpload', 'upload', 'completeUpload'], + }), + new ClassInstrumentation({ + targetClass: QueueBaseEvent, + enabled: true, + methodsToInstrument: ['send', 'batchSend'], + setName: (name, attrs, eventClass) => { + if (attrs.constructor.name) { + return name + '.' + eventClass.constructor.name + } + return name + }, + }), + new ClassInstrumentation({ + targetClass: S3Backend, + enabled: true, + methodsToInstrument: [ + 'getObject', + 'putObject', + 'deleteObject', + 'listObjects', + 'copyObject', + 'headObject', + 'createMultipartUpload', + 'uploadPart', + 'completeMultipartUpload', + 'abortMultipartUpload', + 'listMultipartUploads', + 'listParts', + 'getSignedUrl', + 'createBucket', + 'deleteBucket', + 'listBuckets', + 'getBucketLocation', + 'getBucketVersioning', + 'putBucketVersioning', + 'getBucketLifecycleConfiguration', + 'putBucketLifecycleConfiguration', + 'deleteBucketLifecycle', + 'uploadObject', + 'privateAssetUrl', + ], + }), + new ClassInstrumentation({ + targetClass: StorageKnexDB, + enabled: true, + methodsToInstrument: ['runQuery'], + setName: (name, attrs) => { + if (attrs.queryName) { + return name + '.' + attrs.queryName + } + return name + }, + setAttributes: { + runQuery: (queryName) => { + return { + queryName, + } + }, + }, + }), + new ClassInstrumentation({ + targetClass: TenantConnection, + enabled: true, + methodsToInstrument: ['transaction', 'setScope'], + }), + new ClassInstrumentation({ + targetClass: S3Store, + enabled: true, + methodsToInstrument: [ + 'write', + 'create', + 'remove', + 'getUpload', + 'declareUploadLength', + 'uploadIncompletePart', + 'uploadPart', + 'downloadIncompletePart', + 'uploadParts', + ], + setName: (name) => 'Tus.' + name, + }), + new ClassInstrumentation({ + targetClass: StreamSplitter, + enabled: true, + methodsToInstrument: ['emitEvent'], + setName: (name: string, attrs: any) => { + if (attrs.event) { + return name + '.' + attrs.event + } + return name + }, + setAttributes: { + emitEvent(event) { + return { + part: this.part as any, + event, + } + }, + }, + }), + new ClassInstrumentation({ + targetClass: PgLock, + enabled: true, + methodsToInstrument: ['lock', 'unlock', 'acquireLock'], + }), + new ClassInstrumentation({ + targetClass: Semaphore, + enabled: true, + methodsToInstrument: ['acquire'], + }), + new ClassInstrumentation({ + targetClass: Permit, + enabled: true, + methodsToInstrument: ['release'], + }), + new ClassInstrumentation({ + targetClass: S3Client, + enabled: true, + methodsToInstrument: ['send'], + setAttributes: { + send: (command) => { + return { + operation: command.constructor.name as string, + } + }, + }, + setName: (name, attrs) => 'S3.' + attrs.operation, + }), + new ClassInstrumentation({ + targetClass: Upload, + enabled: true, + methodsToInstrument: [ + 'done', + '__uploadUsingPut', + '__createMultipartUpload', + 'markUploadAsAborted', + ], + }), +] + +export async function loadClassInstrumentations() { + return classInstrumentations +} diff --git a/src/internal/monitoring/otel-instrumentation.ts b/src/internal/monitoring/otel-instrumentation.ts index e5bf9bbd8..de8ea6c85 100644 --- a/src/internal/monitoring/otel-instrumentation.ts +++ b/src/internal/monitoring/otel-instrumentation.ts @@ -1,5 +1,52 @@ +import { + Context, + MeterProvider, + Span, + SpanStatusCode, + TracerProvider, + trace, +} from '@opentelemetry/api' import { Instrumentation, InstrumentationConfig } from '@opentelemetry/instrumentation' -import { trace, Span, SpanStatusCode, TracerProvider, MeterProvider } from '@opentelemetry/api' +import { ReadableSpan, Span as SdkSpan, SpanProcessor } from '@opentelemetry/sdk-trace-base' + +export class TenantSpanProcessor implements SpanProcessor { + private readonly attributesToPropagate: string[] + + constructor(attributesToPropagate: string[] = ['tenant.ref', 'region']) { + this.attributesToPropagate = attributesToPropagate + } + + onStart(span: SdkSpan, parentContext: Context): void { + const parentSpan = trace.getSpan(parentContext) + if (!parentSpan) return + + // Cast to ReadableSpan to access attributes + const parentReadable = parentSpan as unknown as ReadableSpan + const parentAttributes = parentReadable.attributes + + if (!parentAttributes) return + + // Copy specified attributes from parent to child span + for (const attr of this.attributesToPropagate) { + const value = parentAttributes[attr] + if (value !== undefined) { + span.setAttribute(attr, value) + } + } + } + + onEnd(_span: ReadableSpan): void { + // No-op + } + + shutdown(): Promise { + return Promise.resolve() + } + + forceFlush(): Promise { + return Promise.resolve() + } +} interface GenericInstrumentationConfig extends InstrumentationConfig { targetClass: new (...args: any[]) => any @@ -77,8 +124,7 @@ class ClassInstrumentation implements Instrumentation { private patchMethod(proto: any, methodName: string): void { const original = proto[methodName] const instrumentationName = this.instrumentationName - // eslint-disable-next-line @typescript-eslint/no-this-alias - const _this = this + const instrumentation = this proto[methodName] = function (...args: any[]) { const tracer = trace.getTracer(instrumentationName) @@ -92,10 +138,11 @@ class ClassInstrumentation implements Instrumentation { }, async (span: Span) => { try { - const customAttrs = _this._config.setAttributes?.[methodName]?.apply(this, args) || {} + const customAttrs = + instrumentation._config.setAttributes?.[methodName]?.apply(this, args) || {} span.setAttributes(customAttrs) - const spanName = _this._config.setName?.( + const spanName = instrumentation._config.setName?.( `${instrumentationName}.${methodName}`, customAttrs, this @@ -109,9 +156,11 @@ class ClassInstrumentation implements Instrumentation { return result } catch (error) { if (error instanceof Error) { + // Avoid JSON.stringify of full error/stack - just capture message + // Stack traces can be 50KB+ and cause significant GC pressure span.setAttributes({ - error: JSON.stringify({ message: error.message, stack: error.stack }), - stack: error.stack, + 'error.message': error.message, + 'error.name': error.name, }) } diff --git a/src/internal/monitoring/otel-metrics.test.ts b/src/internal/monitoring/otel-metrics.test.ts new file mode 100644 index 000000000..81317ec2b --- /dev/null +++ b/src/internal/monitoring/otel-metrics.test.ts @@ -0,0 +1,263 @@ +interface OTelGlobalState { + __otelMetricsShutdown?: () => Promise +} + +import { vi } from 'vitest' + +const mockedMetricsModules = [ + '../../config', + '@internal/monitoring/logger', + '@internal/monitoring/system', + '@opentelemetry/api', + '@opentelemetry/exporter-metrics-otlp-grpc', + '@opentelemetry/exporter-prometheus', + '@opentelemetry/host-metrics', + '@opentelemetry/instrumentation', + '@opentelemetry/instrumentation-runtime-node', + '@opentelemetry/resources', + '@opentelemetry/sdk-metrics', +] as const + +async function importOtelMetricsModule() { + await import('./otel-metrics') + return (globalThis as typeof globalThis & OTelGlobalState).__otelMetricsShutdown +} + +describe('otel metrics', () => { + const originalOtelExporterEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT + const originalOtelMetricsEndpoint = process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT + const originalOtelMetricsHeaders = process.env.OTEL_EXPORTER_OTLP_METRICS_HEADERS + + afterEach(async () => { + const otelGlobalState = globalThis as typeof globalThis & OTelGlobalState + + if (otelGlobalState.__otelMetricsShutdown) { + await otelGlobalState.__otelMetricsShutdown() + delete otelGlobalState.__otelMetricsShutdown + } + + if (originalOtelExporterEndpoint === undefined) { + delete process.env.OTEL_EXPORTER_OTLP_ENDPOINT + } else { + process.env.OTEL_EXPORTER_OTLP_ENDPOINT = originalOtelExporterEndpoint + } + + if (originalOtelMetricsEndpoint === undefined) { + delete process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT + } else { + process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = originalOtelMetricsEndpoint + } + + if (originalOtelMetricsHeaders === undefined) { + delete process.env.OTEL_EXPORTER_OTLP_METRICS_HEADERS + } else { + process.env.OTEL_EXPORTER_OTLP_METRICS_HEADERS = originalOtelMetricsHeaders + } + + for (const moduleId of mockedMetricsModules) { + vi.doUnmock(moduleId) + } + + vi.restoreAllMocks() + vi.resetModules() + }) + + test('still shuts down meter provider when unregister throws', async () => { + const shutdown = vi.fn().mockResolvedValue(undefined) + const unregisterError = new Error('metrics unregister failed') + const unregisterMetricInstrumentations = vi.fn(() => { + throw unregisterError + }) + const registerInstrumentations = vi.fn(() => unregisterMetricInstrumentations) + const HostMetrics = vi.fn(function () { + return { + start: vi.fn(), + } + }) + const MeterProvider = vi.fn(function () { + return { + shutdown, + } + }) + const PrometheusExporter = vi.fn(function () { + return { + getMetricsRequestHandler: vi.fn(), + } + }) + const RuntimeNodeInstrumentation = vi.fn(function () { + return {} + }) + const StorageNodeInstrumentation = vi.fn(function () { + return {} + }) + const logger = { + info: vi.fn(), + } + const logSchema = { + error: vi.fn(), + info: vi.fn(), + } + + vi.doMock('../../config', () => ({ + getConfig: vi.fn(() => ({ + version: 'test-version', + otelMetricsExportIntervalMs: 1000, + otelMetricsEnabled: true, + otelMetricsTemporality: 'CUMULATIVE', + prometheusMetricsEnabled: true, + region: 'local', + })), + })) + vi.doMock('@internal/monitoring/logger', () => ({ + logger, + logSchema, + })) + vi.doMock('@internal/monitoring/system', () => ({ + StorageNodeInstrumentation, + })) + vi.doMock('@opentelemetry/api', () => ({ + metrics: { + setGlobalMeterProvider: vi.fn(), + }, + })) + vi.doMock('@opentelemetry/exporter-metrics-otlp-grpc', () => ({ + OTLPMetricExporter: vi.fn(function () { + return {} + }), + })) + vi.doMock('@opentelemetry/exporter-prometheus', () => ({ + PrometheusExporter, + })) + vi.doMock('@opentelemetry/host-metrics', () => ({ + HostMetrics, + })) + vi.doMock('@opentelemetry/instrumentation', () => ({ + registerInstrumentations, + })) + vi.doMock('@opentelemetry/instrumentation-runtime-node', () => ({ + RuntimeNodeInstrumentation, + })) + vi.doMock('@opentelemetry/resources', () => ({ + resourceFromAttributes: vi.fn(() => ({})), + })) + vi.doMock('@opentelemetry/sdk-metrics', () => ({ + AggregationTemporality: { + CUMULATIVE: 'CUMULATIVE', + DELTA: 'DELTA', + }, + AggregationType: { + DROP: 'DROP', + EXPLICIT_BUCKET_HISTOGRAM: 'EXPLICIT_BUCKET_HISTOGRAM', + }, + MeterProvider, + PeriodicExportingMetricReader: vi.fn(), + })) + + const shutdownOtelMetrics = await importOtelMetricsModule() + + await expect(shutdownOtelMetrics?.()).resolves.toBeUndefined() + + expect(unregisterMetricInstrumentations).toHaveBeenCalledTimes(1) + expect(shutdown).toHaveBeenCalledTimes(1) + expect(unregisterMetricInstrumentations.mock.invocationCallOrder[0]).toBeLessThan( + shutdown.mock.invocationCallOrder[0] + ) + expect(logSchema.error).toHaveBeenCalledWith( + logger, + '[OTel Metrics] Failed to unregister metric instrumentations', + expect.objectContaining({ type: 'otel-metrics', error: unregisterError }) + ) + }) + + test('does not create a Prometheus reader when Prometheus metrics are disabled', async () => { + delete process.env.OTEL_EXPORTER_OTLP_ENDPOINT + delete process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT + + const registerInstrumentations = vi.fn(() => vi.fn()) + const HostMetrics = vi.fn(function () { + return { + start: vi.fn(), + } + }) + const MeterProvider = vi.fn(function () { + return { + shutdown: vi.fn().mockResolvedValue(undefined), + } + }) + const PrometheusExporter = vi.fn(function () { + return { + getMetricsRequestHandler: vi.fn(), + } + }) + const RuntimeNodeInstrumentation = vi.fn(function () { + return {} + }) + const StorageNodeInstrumentation = vi.fn(function () { + return {} + }) + + vi.doMock('../../config', () => ({ + getConfig: vi.fn(() => ({ + version: 'test-version', + otelMetricsExportIntervalMs: 1000, + otelMetricsEnabled: true, + otelMetricsTemporality: 'CUMULATIVE', + prometheusMetricsEnabled: false, + region: 'local', + })), + })) + vi.doMock('@internal/monitoring/logger', () => ({ + logger: { info: vi.fn() }, + logSchema: { error: vi.fn(), info: vi.fn() }, + })) + vi.doMock('@internal/monitoring/system', () => ({ + StorageNodeInstrumentation, + })) + vi.doMock('@opentelemetry/api', () => ({ + metrics: { + setGlobalMeterProvider: vi.fn(), + }, + })) + vi.doMock('@opentelemetry/exporter-metrics-otlp-grpc', () => ({ + OTLPMetricExporter: vi.fn(function () { + return {} + }), + })) + vi.doMock('@opentelemetry/exporter-prometheus', () => ({ + PrometheusExporter, + })) + vi.doMock('@opentelemetry/host-metrics', () => ({ + HostMetrics, + })) + vi.doMock('@opentelemetry/instrumentation', () => ({ + registerInstrumentations, + })) + vi.doMock('@opentelemetry/instrumentation-runtime-node', () => ({ + RuntimeNodeInstrumentation, + })) + vi.doMock('@opentelemetry/resources', () => ({ + resourceFromAttributes: vi.fn(() => ({})), + })) + vi.doMock('@opentelemetry/sdk-metrics', () => ({ + AggregationTemporality: { + CUMULATIVE: 'CUMULATIVE', + DELTA: 'DELTA', + }, + AggregationType: { + DROP: 'DROP', + EXPLICIT_BUCKET_HISTOGRAM: 'EXPLICIT_BUCKET_HISTOGRAM', + }, + MeterProvider, + PeriodicExportingMetricReader: vi.fn(), + })) + + await importOtelMetricsModule() + + expect(PrometheusExporter).not.toHaveBeenCalled() + expect(MeterProvider).toHaveBeenCalledWith( + expect.objectContaining({ + readers: [], + }) + ) + }) +}) diff --git a/src/internal/monitoring/otel-metrics.ts b/src/internal/monitoring/otel-metrics.ts new file mode 100644 index 000000000..ab396601a --- /dev/null +++ b/src/internal/monitoring/otel-metrics.ts @@ -0,0 +1,293 @@ +import * as grpc from '@grpc/grpc-js' +import { logger, logSchema } from '@internal/monitoring/logger' +import { StorageNodeInstrumentation } from '@internal/monitoring/system' +import { metrics } from '@opentelemetry/api' +import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc' +import { PrometheusExporter } from '@opentelemetry/exporter-prometheus' +import { HostMetrics } from '@opentelemetry/host-metrics' +import { registerInstrumentations } from '@opentelemetry/instrumentation' +import { RuntimeNodeInstrumentation } from '@opentelemetry/instrumentation-runtime-node' +import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base' +import { resourceFromAttributes } from '@opentelemetry/resources' +import { + AggregationTemporality, + AggregationType, + MeterProvider, + PeriodicExportingMetricReader, +} from '@opentelemetry/sdk-metrics' +import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions' +import { FastifyReply, FastifyRequest } from 'fastify' +import * as os from 'os' +import { getConfig } from '../../config' + +const { + version, + otelMetricsExportIntervalMs, + otelMetricsEnabled, + otelMetricsTemporality, + prometheusMetricsEnabled, + region, +} = getConfig() + +let prometheusExporter: PrometheusExporter | undefined +let meterProvider: MeterProvider | undefined +let metricsShutdownPromise: Promise | undefined +let unregisterMetricInstrumentations: (() => void) | undefined + +interface OTelMetricsGlobalState { + __otelMetricsShutdown?: () => Promise +} + +function unregisterMetricInstrumentation(unregister: (() => void) | undefined) { + if (!unregister) { + return + } + + try { + unregister() + } catch (error) { + logSchema.error(logger, '[OTel Metrics] Failed to unregister metric instrumentations', { + type: 'otel-metrics', + error, + }) + } +} + +// ============================================================================= +// Shared config +// ============================================================================= +const instance = os.hostname() +const headersEnv = process.env.OTEL_EXPORTER_OTLP_METRICS_HEADERS || '' +const otlpEndpoint = + process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_ENDPOINT + +const exporterHeaders = headersEnv + .split(',') + .filter(Boolean) + .reduce( + (all, header) => { + const [name, value] = header.split('=') + all[name] = value + return all + }, + {} as Record + ) + +const grpcMetadata = new grpc.Metadata() +Object.keys(exporterHeaders).forEach((key) => { + grpcMetadata.set(key, exporterHeaders[key]) +}) + +const resource = resourceFromAttributes({ + [ATTR_SERVICE_NAME]: 'storage_api', + [ATTR_SERVICE_VERSION]: version, + 'metric.version': '1', + region, + instance, +}) + +// Bucket boundaries for duration histograms (in seconds) +const durationBuckets = [ + 0.0005, 0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, +] + +const histogramAggregation = { + type: AggregationType.EXPLICIT_BUCKET_HISTOGRAM, + options: { boundaries: durationBuckets }, +} as const + +const dropAggregation = { type: AggregationType.DROP } as const + +// Views — custom histogram buckets + drop auto-instrumentation duplicates. +// Tenant attribute stripping is handled by registerMetric() in metrics.ts. +const views = [ + { + meterName: 'storage-api', + instrumentName: 'http_request_duration_seconds', + aggregation: histogramAggregation, + }, + { + meterName: 'storage-api', + instrumentName: 'database_query_performance_seconds', + aggregation: histogramAggregation, + }, + { + meterName: 'storage-api', + instrumentName: 'db_connection_acquire_seconds', + aggregation: histogramAggregation, + }, + { + meterName: 'storage-api', + instrumentName: 'queue_job_scheduled_time_seconds', + aggregation: histogramAggregation, + }, + { + meterName: 'storage-api', + instrumentName: 's3_upload_part_seconds', + aggregation: histogramAggregation, + }, + // Drop duplicate HTTP metrics from auto-instrumentations — we have our own in metrics.ts + { + meterName: '@opentelemetry/instrumentation-http', + instrumentName: '*', + aggregation: dropAggregation, + }, + // Drop any Fastify metrics from auto-instrumentations (now using @fastify/otel for traces only) + { + meterName: '@fastify/otel', + instrumentName: '*', + aggregation: dropAggregation, + }, +] + +// ============================================================================= +// Shutdown +// ============================================================================= +export async function shutdownOtelMetrics(): Promise { + if (metricsShutdownPromise) { + await metricsShutdownPromise + return + } + + if (!meterProvider) { + return + } + + const provider = meterProvider + metricsShutdownPromise = (async () => { + logSchema.info(logger, '[OTel Metrics] Stopping', { + type: 'otel-metrics', + }) + + const unregister = unregisterMetricInstrumentations + unregisterMetricInstrumentations = undefined + unregisterMetricInstrumentation(unregister) + + try { + await provider.shutdown() + logSchema.info(logger, '[OTel Metrics] Shutdown complete', { + type: 'otel-metrics', + }) + } catch (error) { + logSchema.error(logger, '[OTel Metrics] Shutdown error', { + type: 'otel-metrics', + error, + }) + } finally { + if (meterProvider === provider) { + meterProvider = undefined + } + prometheusExporter = undefined + metricsShutdownPromise = undefined + } + })() + + await metricsShutdownPromise +} + +;(globalThis as typeof globalThis & OTelMetricsGlobalState).__otelMetricsShutdown = + shutdownOtelMetrics + +// ============================================================================= +// /metrics endpoint handler +// ============================================================================= +export async function handleMetricsRequest( + request: FastifyRequest, + reply: FastifyReply +): Promise { + if (!prometheusExporter) { + reply.status(404).send('Metrics not enabled') + return + } + + const req = request.raw + const res = reply.raw + + reply.hijack() + prometheusExporter.getMetricsRequestHandler(req, res) + + return Promise.resolve() +} + +// ============================================================================= +// Initialize at import time +// ============================================================================= +if (otelMetricsEnabled) { + const readers = [] + + if (otlpEndpoint) { + const otlpExporter = new OTLPMetricExporter({ + url: otlpEndpoint, + compression: process.env.OTEL_EXPORTER_OTLP_COMPRESSION as CompressionAlgorithm, + headers: exporterHeaders, + metadata: grpcMetadata, + temporalityPreference: + otelMetricsTemporality === 'DELTA' + ? AggregationTemporality.DELTA + : AggregationTemporality.CUMULATIVE, + }) + + readers.push( + new PeriodicExportingMetricReader({ + exporter: otlpExporter, + exportIntervalMillis: otelMetricsExportIntervalMs, + }) + ) + } + + if (prometheusMetricsEnabled) { + prometheusExporter = new PrometheusExporter({ + prefix: 'storage_api', + preventServerStart: true, + withResourceConstantLabels: /^(region|instance|metric\.version)$/, + }) + readers.push(prometheusExporter) + } + + meterProvider = new MeterProvider({ + resource, + readers, + views, + }) + + // Register as global provider IMMEDIATELY so metrics.ts instruments work + metrics.setGlobalMeterProvider(meterProvider) + + logger.info( + { type: 'otel-metrics', otlpEndpoint, exportIntervalMs: otelMetricsExportIntervalMs }, + '[OTel Metrics] Initializing' + ) + + if (otlpEndpoint) { + logSchema.info(logger, '[OTel Metrics] OTLP exporter configured', { + type: 'otel-metrics', + }) + } + + // Initialize host metrics for Node.js runtime metrics + const hostMetrics = new HostMetrics({ + meterProvider, + name: 'storage-api-host-metrics', + metricGroups: ['process.cpu', 'process.memory'], + }) + hostMetrics.start() + + // Register Node.js runtime instrumentations + unregisterMetricInstrumentations = registerInstrumentations({ + meterProvider, + instrumentations: [ + new RuntimeNodeInstrumentation(), + new StorageNodeInstrumentation({ labels: { region, instance } }), + ], + }) + + logSchema.info(logger, '[OTel Metrics] Initialized', { type: 'otel-metrics' }) + + // Graceful shutdown + const shutdown = () => { + void shutdownOtelMetrics() + } + + process.once('SIGTERM', shutdown) + process.once('SIGINT', shutdown) +} diff --git a/src/internal/monitoring/otel-processor.ts b/src/internal/monitoring/otel-processor.ts deleted file mode 100644 index d13fa2533..000000000 --- a/src/internal/monitoring/otel-processor.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { SpanProcessor, ReadableSpan } from '@opentelemetry/sdk-trace-base' -import TTLCache from '@isaacs/ttlcache' - -interface Trace { - id: string - rootSpanId: string - spans: Span[] -} - -export interface Span { - item: ReadableSpan - children: Span[] -} - -export class TraceCollectorSpanProcessor implements SpanProcessor { - private traces: TTLCache - - constructor() { - this.traces = new TTLCache({ - ttl: 120 * 1000, // 120 seconds TTL - noUpdateTTL: true, - noDisposeOnSet: true, - }) - } - - export() { - // no-op - } - - onStart(span: ReadableSpan): void { - const traceId = span.spanContext().traceId - const spanId = span.spanContext().spanId - - // No action needed on start - if (!span.parentSpanId) { - const hasTrace = this.traces.has(traceId) - - if (!hasTrace) { - this.traces.set(traceId, { - id: traceId, - spans: [ - { - item: span, - children: [], - }, - ], - rootSpanId: spanId, - }) - } - - return - } - - const trace = this.traces.get(traceId) - - if (trace) { - this.addChildSpan(trace.spans, span.parentSpanId, { item: span, children: [] }) - } - } - - onEnd(span: ReadableSpan): void { - // no-op - } - - shutdown(): Promise { - this.traces.clear() - return Promise.resolve() - } - - forceFlush(): Promise { - this.traces.clear() - return Promise.resolve() - } - - getSpansForTrace(traceId: string): Span[] { - return this.traces.get(traceId)?.spans || [] - } - - clearTrace(traceId: string): void { - this.traces.delete(traceId) - } - - private addChildSpan(spans: Span[], parentId: string, childSpan: Span): void { - for (const span of spans) { - if (span.item.spanContext().spanId === parentId) { - span.children.push(childSpan) - return - } - if (span.children.length > 0) { - this.addChildSpan(span.children, parentId, childSpan) - } - } - } -} - -export const traceCollector = new TraceCollectorSpanProcessor() diff --git a/src/internal/monitoring/otel-tracing.test.ts b/src/internal/monitoring/otel-tracing.test.ts new file mode 100644 index 000000000..27212c2e3 --- /dev/null +++ b/src/internal/monitoring/otel-tracing.test.ts @@ -0,0 +1,264 @@ +interface OTelGlobalState { + __otelTracingShutdown?: () => Promise +} + +import { vi } from 'vitest' + +const mockedTracingModules = [ + '@opentelemetry/sdk-node', + '@opentelemetry/instrumentation', + '@opentelemetry/auto-instrumentations-node', + '@fastify/otel', + '@opentelemetry/exporter-trace-otlp-grpc', + '@internal/monitoring/logger', + './otel-class-instrumentations', +] as const + +async function importOtelTracingModule() { + await import('./otel-tracing') + return (globalThis as typeof globalThis & OTelGlobalState).__otelTracingShutdown +} + +describe('otel tracing bootstrap', () => { + const originalTracingEnabled = process.env.TRACING_ENABLED + const originalTraceEndpoint = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT + + beforeEach(() => { + vi.resetModules() + vi.clearAllMocks() + process.env.TRACING_ENABLED = 'true' + process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://127.0.0.1:4317' + }) + + afterEach(async () => { + const otelGlobalState = globalThis as typeof globalThis & OTelGlobalState + + if (otelGlobalState.__otelTracingShutdown) { + await otelGlobalState.__otelTracingShutdown() + delete otelGlobalState.__otelTracingShutdown + } + + if (originalTracingEnabled === undefined) { + delete process.env.TRACING_ENABLED + } else { + process.env.TRACING_ENABLED = originalTracingEnabled + } + + if (originalTraceEndpoint === undefined) { + delete process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT + } else { + process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = originalTraceEndpoint + } + + for (const moduleId of mockedTracingModules) { + vi.doUnmock(moduleId) + } + + vi.restoreAllMocks() + }) + + test('does not let tracing sdk create a hidden metrics pipeline', async () => { + const start = vi.fn() + const shutdown = vi.fn().mockResolvedValue(undefined) + const NodeSDK = vi.fn(function () { + return { + start, + shutdown, + } + }) + const registerInstrumentations = vi.fn(() => vi.fn()) + const getNodeAutoInstrumentations = vi.fn().mockReturnValue([]) + const FastifyOtelInstrumentation = vi.fn(function (config) { + return { + instrumentationName: '@fastify/otel', + config, + } + }) + const OTLPTraceExporter = vi.fn(function () { + return {} + }) + + vi.doMock('@opentelemetry/sdk-node', () => ({ + NodeSDK, + })) + vi.doMock('@opentelemetry/instrumentation', () => ({ + registerInstrumentations, + })) + vi.doMock('@opentelemetry/auto-instrumentations-node', () => ({ + getNodeAutoInstrumentations, + })) + vi.doMock('@fastify/otel', () => ({ + FastifyOtelInstrumentation, + })) + vi.doMock('@opentelemetry/exporter-trace-otlp-grpc', () => ({ + OTLPTraceExporter, + })) + + await importOtelTracingModule() + + expect(NodeSDK).toHaveBeenCalledWith( + expect.objectContaining({ + metricReaders: [], + }) + ) + + expect(getNodeAutoInstrumentations).toHaveBeenCalledWith( + expect.objectContaining({ + '@opentelemetry/instrumentation-runtime-node': expect.objectContaining({ + enabled: false, + }), + }) + ) + + expect(registerInstrumentations).toHaveBeenCalled() + expect(start).toHaveBeenCalled() + }) + + test('does not register class instrumentations after shutdown starts', async () => { + const start = vi.fn() + const shutdown = vi.fn().mockResolvedValue(undefined) + const unregisterTracingInstrumentations = vi.fn() + const unregisterClassInstrumentations = vi.fn() + const NodeSDK = vi.fn(function () { + return { + start, + shutdown, + } + }) + const registerInstrumentations = vi + .fn() + .mockReturnValueOnce(unregisterTracingInstrumentations) + .mockReturnValueOnce(unregisterClassInstrumentations) + const getNodeAutoInstrumentations = vi.fn().mockReturnValue([]) + const FastifyOtelInstrumentation = vi.fn(function (config) { + return { + instrumentationName: '@fastify/otel', + config, + } + }) + const OTLPTraceExporter = vi.fn(function () { + return {} + }) + const classInstrumentationsDeferred = Promise.withResolvers<{ + classInstrumentations: unknown[] + }>() + + vi.doMock('@opentelemetry/sdk-node', () => ({ + NodeSDK, + })) + vi.doMock('@opentelemetry/instrumentation', () => ({ + registerInstrumentations, + })) + vi.doMock('@opentelemetry/auto-instrumentations-node', () => ({ + getNodeAutoInstrumentations, + })) + vi.doMock('@fastify/otel', () => ({ + FastifyOtelInstrumentation, + })) + vi.doMock('@opentelemetry/exporter-trace-otlp-grpc', () => ({ + OTLPTraceExporter, + })) + vi.doMock('./otel-class-instrumentations', () => ({ + loadClassInstrumentations: vi.fn(() => classInstrumentationsDeferred.promise), + })) + + const shutdownOtelTracing = await importOtelTracingModule() + + const shutdownPromise = shutdownOtelTracing?.() + + classInstrumentationsDeferred.resolve({ classInstrumentations: [] }) + + await shutdownPromise + + expect(registerInstrumentations).toHaveBeenCalledTimes(1) + expect(unregisterTracingInstrumentations).toHaveBeenCalledTimes(1) + expect(unregisterClassInstrumentations).not.toHaveBeenCalled() + expect(shutdown).toHaveBeenCalledTimes(1) + }) + + test('still shuts down sdk when unregister callbacks throw', async () => { + const start = vi.fn() + const shutdown = vi.fn().mockResolvedValue(undefined) + const unregisterTracingError = new Error('tracing unregister failed') + const unregisterClassError = new Error('class unregister failed') + const unregisterTracingInstrumentations = vi.fn(() => { + throw unregisterTracingError + }) + const unregisterClassInstrumentations = vi.fn(() => { + throw unregisterClassError + }) + const NodeSDK = vi.fn(function () { + return { + start, + shutdown, + } + }) + const registerInstrumentations = vi + .fn() + .mockReturnValueOnce(unregisterTracingInstrumentations) + .mockReturnValueOnce(unregisterClassInstrumentations) + const getNodeAutoInstrumentations = vi.fn().mockReturnValue([]) + const FastifyOtelInstrumentation = vi.fn(function (config) { + return { + instrumentationName: '@fastify/otel', + config, + } + }) + const OTLPTraceExporter = vi.fn(function () { + return {} + }) + const classInstrumentationsDeferred = Promise.withResolvers() + const logSchema = { + error: vi.fn(), + info: vi.fn(), + warning: vi.fn(), + } + + vi.doMock('@opentelemetry/sdk-node', () => ({ + NodeSDK, + })) + vi.doMock('@opentelemetry/instrumentation', () => ({ + registerInstrumentations, + })) + vi.doMock('@opentelemetry/auto-instrumentations-node', () => ({ + getNodeAutoInstrumentations, + })) + vi.doMock('@fastify/otel', () => ({ + FastifyOtelInstrumentation, + })) + vi.doMock('@opentelemetry/exporter-trace-otlp-grpc', () => ({ + OTLPTraceExporter, + })) + vi.doMock('@internal/monitoring/logger', () => ({ + logger: {}, + logSchema, + })) + vi.doMock('./otel-class-instrumentations', () => ({ + loadClassInstrumentations: vi.fn(() => classInstrumentationsDeferred.promise), + })) + + const shutdownOtelTracing = await importOtelTracingModule() + + classInstrumentationsDeferred.resolve([]) + await classInstrumentationsDeferred.promise + await new Promise((resolve) => setImmediate(resolve)) + + expect(registerInstrumentations).toHaveBeenCalledTimes(2) + + await expect(shutdownOtelTracing?.()).resolves.toBeUndefined() + + expect(unregisterClassInstrumentations).toHaveBeenCalledTimes(1) + expect(unregisterTracingInstrumentations).toHaveBeenCalledTimes(1) + expect(shutdown).toHaveBeenCalledTimes(1) + expect(logSchema.error).toHaveBeenCalledWith( + {}, + '[Otel] Failed to unregister class instrumentations', + expect.objectContaining({ type: 'otel', error: unregisterClassError }) + ) + expect(logSchema.error).toHaveBeenCalledWith( + {}, + '[Otel] Failed to unregister tracing instrumentations', + expect.objectContaining({ type: 'otel', error: unregisterTracingError }) + ) + }) +}) diff --git a/src/internal/monitoring/otel-tracing.ts b/src/internal/monitoring/otel-tracing.ts new file mode 100644 index 000000000..2ed4fd97a --- /dev/null +++ b/src/internal/monitoring/otel-tracing.ts @@ -0,0 +1,275 @@ +import { getConfig } from '../../config' + +const { + version, + requestTraceHeader, + isMultitenant, + requestXForwardedHostRegExp, + tenantId: defaultTenantId, + region, + storageS3InternalTracesEnabled, +} = getConfig() + +import { FastifyOtelInstrumentation } from '@fastify/otel' +import * as grpc from '@grpc/grpc-js' +import { logger, logSchema } from '@internal/monitoring/logger' +import { TenantSpanProcessor } from '@internal/monitoring/otel-instrumentation' +import { trace } from '@opentelemetry/api' +import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node' +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc' +import { registerInstrumentations } from '@opentelemetry/instrumentation' +import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base' +import { resourceFromAttributes } from '@opentelemetry/resources' +import { NodeSDK } from '@opentelemetry/sdk-node' +import { BatchSpanProcessor, SpanExporter, SpanProcessor } from '@opentelemetry/sdk-trace-base' +import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions' + +const tracingEnabled = process.env.TRACING_ENABLED === 'true' +const headersEnv = process.env.OTEL_EXPORTER_OTLP_TRACES_HEADERS || '' + +const exporterHeaders = headersEnv + .split(',') + .filter(Boolean) + .reduce( + (all, header) => { + const [name, value] = header.split('=') + all[name] = value + return all + }, + {} as Record + ) + +const grpcMetadata = new grpc.Metadata() +Object.keys(exporterHeaders).forEach((key) => { + grpcMetadata.set(key, exporterHeaders[key]) +}) + +const endpoint = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT +let traceExporter: SpanExporter | undefined = undefined +let tracingSdk: NodeSDK | undefined +let tracingShutdownPromise: Promise | undefined +let unregisterTracingInstrumentations: (() => void) | undefined +let unregisterClassInstrumentations: (() => void) | undefined +let classInstrumentationsImportPromise: Promise | undefined +let tracingShutdownRequested = false + +interface OTelTracingGlobalState { + __otelTracingShutdown?: () => Promise +} + +function unregisterTracingInstrumentation( + unregister: (() => void) | undefined, + name: 'class' | 'tracing' +) { + if (!unregister) { + return + } + + try { + unregister() + } catch (error) { + logSchema.error(logger, `[Otel] Failed to unregister ${name} instrumentations`, { + type: 'otel', + error, + }) + } +} + +if (tracingEnabled && endpoint) { + // Create an OTLP trace exporter + traceExporter = new OTLPTraceExporter({ + url: endpoint, + compression: process.env.OTEL_EXPORTER_OTLP_COMPRESSION as CompressionAlgorithm, + headers: exporterHeaders, + metadata: grpcMetadata, + }) +} + +const spanProcessors: SpanProcessor[] = [] + +if (tracingEnabled && traceExporter) { + spanProcessors.push(new TenantSpanProcessor()) + spanProcessors.push(new BatchSpanProcessor(traceExporter)) +} else if (tracingEnabled) { + logSchema.warning( + logger, + '[Otel] TRACING_ENABLED=true but no OTLP trace endpoint configured; skipping tracing SDK startup', + { + type: 'otel', + } + ) +} + +if (tracingEnabled && traceExporter && spanProcessors.length > 0) { + // Configure the OpenTelemetry Node SDK + tracingSdk = new NodeSDK({ + resource: resourceFromAttributes({ + [ATTR_SERVICE_NAME]: 'storage', + [ATTR_SERVICE_VERSION]: version, + }), + spanProcessors, + metricReaders: [], + }) + + // Initialize the OpenTelemetry Node SDK + tracingSdk.start() + tracingShutdownRequested = false + + const ignoreRoutes = ['/metrics', '/status', '/health', '/healthcheck'] + const tracingInstrumentations = [ + // @fastify/otel replaces @opentelemetry/instrumentation-fastify + // It auto-sets http.route, http.request.method, url.path on spans. + // Other attributes (tenant.ref, trace.mode, http.operation) are set + // in Fastify hooks via request.opentelemetry().span. + new FastifyOtelInstrumentation({ + enabled: true, + registerOnInitialization: true, + ignorePaths: (routeOpts) => { + return ignoreRoutes.includes(routeOpts.url) + }, + }), + getNodeAutoInstrumentations({ + '@opentelemetry/instrumentation-http': { + enabled: true, + ignoreIncomingRequestHook: (req) => { + return ignoreRoutes.some((url) => req.url?.includes(url)) ?? false + }, + ignoreOutgoingRequestHook: (req) => { + // Skip OTEL instrumentation for S3 Tables requests to avoid injecting + // unsupported headers (baggage, traceparent, tracestate) + const host = req.hostname || req.host || '' + return host.includes('.s3tables.') || host.includes('--table-s3') + }, + startIncomingSpanHook: (req) => { + let tenantId = '' + if (isMultitenant) { + if (requestXForwardedHostRegExp) { + const serverRequest = req + const xForwardedHost = serverRequest.headers['x-forwarded-host'] + if (typeof xForwardedHost !== 'string') return {} + const result = xForwardedHost.match(requestXForwardedHostRegExp) + if (!result) return {} + tenantId = result[1] + } + } else { + tenantId = defaultTenantId + } + + return { + 'tenant.ref': tenantId, + region, + } + }, + headersToSpanAttributes: { + client: { + requestHeaders: requestTraceHeader ? [requestTraceHeader] : [], + }, + server: { + requestHeaders: requestTraceHeader ? [requestTraceHeader] : [], + }, + }, + }, + '@opentelemetry/instrumentation-fs': { + enabled: false, + }, + '@opentelemetry/instrumentation-aws-sdk': { + enabled: storageS3InternalTracesEnabled, + }, + '@opentelemetry/instrumentation-pg': { + enabled: true, + requireParentSpan: true, + }, + '@opentelemetry/instrumentation-knex': { + enabled: true, + }, + '@opentelemetry/instrumentation-runtime-node': { + enabled: false, + }, + }), + ] + + unregisterTracingInstrumentations = registerInstrumentations({ + tracerProvider: trace.getTracerProvider(), + instrumentations: tracingInstrumentations, + }) + + const sdk = tracingSdk + + // Load class instrumentations after SDK starts to avoid loading http/metrics.ts too early + classInstrumentationsImportPromise = import('./otel-class-instrumentations') + .then(({ loadClassInstrumentations }) => loadClassInstrumentations()) + .then((classInstrumentations) => { + if (tracingShutdownRequested || tracingSdk !== sdk) { + return + } + + unregisterClassInstrumentations = registerInstrumentations({ + tracerProvider: trace.getTracerProvider(), + instrumentations: classInstrumentations, + }) + }) + .catch((error) => { + logSchema.error(logger, '[Otel] Failed to load class instrumentations', { + type: 'otel', + error, + }) + }) + + const shutdownOtelTracing = async () => { + if (tracingShutdownPromise) { + await tracingShutdownPromise + return + } + + const sdk = tracingSdk + if (!sdk) { + return + } + + tracingShutdownRequested = true + + tracingShutdownPromise = (async () => { + logSchema.info(logger, '[Otel] Stopping', { + type: 'otel', + }) + + try { + await classInstrumentationsImportPromise + classInstrumentationsImportPromise = undefined + + unregisterTracingInstrumentation(unregisterClassInstrumentations, 'class') + unregisterClassInstrumentations = undefined + unregisterTracingInstrumentation(unregisterTracingInstrumentations, 'tracing') + unregisterTracingInstrumentations = undefined + + await sdk.shutdown() + + logSchema.info(logger, '[Otel] Exited', { + type: 'otel', + }) + } catch (error) { + logSchema.error(logger, '[Otel] Shutdown error', { + type: 'otel', + error, + }) + } finally { + if (tracingSdk === sdk) { + tracingSdk = undefined + } + tracingShutdownRequested = false + classInstrumentationsImportPromise = undefined + tracingShutdownPromise = undefined + } + })() + + await tracingShutdownPromise + } + + ;(globalThis as typeof globalThis & OTelTracingGlobalState).__otelTracingShutdown = + shutdownOtelTracing + + // Gracefully shutdown the SDK on process exit + process.once('SIGTERM', () => { + void shutdownOtelTracing() + }) +} diff --git a/src/internal/monitoring/otel.ts b/src/internal/monitoring/otel.ts deleted file mode 100644 index 4113d4f62..000000000 --- a/src/internal/monitoring/otel.ts +++ /dev/null @@ -1,358 +0,0 @@ -import { getConfig } from '../../config' - -const { - version, - requestTraceHeader, - isMultitenant, - requestXForwardedHostRegExp, - tenantId: defaultTenantId, - region, -} = getConfig() - -import { S3Client } from '@aws-sdk/client-s3' -import { NodeSDK } from '@opentelemetry/sdk-node' -import { Resource } from '@opentelemetry/resources' -import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions' -import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc' -import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node' -import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base' -import { SpanExporter, BatchSpanProcessor, SpanProcessor } from '@opentelemetry/sdk-trace-base' -import * as grpc from '@grpc/grpc-js' -import { HttpInstrumentation } from '@opentelemetry/instrumentation-http' -import { logger, logSchema } from '@internal/monitoring/logger' -import { traceCollector } from '@internal/monitoring/otel-processor' -import { ClassInstrumentation } from './otel-instrumentation' -import { ObjectStorage } from '@storage/object' -import { Uploader } from '@storage/uploader' -import { Storage } from '@storage/storage' -import { Event as QueueBaseEvent } from '@internal/queue' -import { S3Backend } from '@storage/backend' -import { StorageKnexDB } from '@storage/database' -import { TenantConnection } from '@internal/database' -import { S3Store } from '@tus/s3-store' -import { Upload } from '@aws-sdk/lib-storage' -import { StreamSplitter } from '@tus/server' -import { PgLock } from '@storage/protocols/tus' -import { Semaphore, Permit } from '@shopify/semaphore' - -const tracingEnabled = process.env.TRACING_ENABLED === 'true' -const headersEnv = process.env.OTEL_EXPORTER_OTLP_TRACES_HEADERS || '' -const enableLogTraces = ['debug', 'logs'].includes(process.env.TRACING_MODE || '') - -const exporterHeaders = headersEnv - .split(',') - .filter(Boolean) - .reduce((all, header) => { - const [name, value] = header.split('=') - all[name] = value - return all - }, {} as Record) - -const grpcMetadata = new grpc.Metadata() -Object.keys(exporterHeaders).forEach((key) => { - grpcMetadata.set(key, exporterHeaders[key]) -}) - -const endpoint = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT -let traceExporter: SpanExporter | undefined = undefined - -if (tracingEnabled && endpoint) { - // Create an OTLP trace exporter - traceExporter = new OTLPTraceExporter({ - url: endpoint, - compression: process.env.OTEL_EXPORTER_OTLP_COMPRESSION as CompressionAlgorithm, - headers: exporterHeaders, - metadata: grpcMetadata, - }) -} - -// Create a BatchSpanProcessor using the trace exporter -const batchProcessor = traceExporter ? new BatchSpanProcessor(traceExporter) : undefined - -const spanProcessors: SpanProcessor[] = [] - -if (batchProcessor) { - spanProcessors.push(batchProcessor) -} - -if (enableLogTraces) { - spanProcessors.push(traceCollector) -} - -if (tracingEnabled && spanProcessors.length > 0) { - // Configure the OpenTelemetry Node SDK - const sdk = new NodeSDK({ - resource: new Resource({ - [ATTR_SERVICE_NAME]: 'storage', - [ATTR_SERVICE_VERSION]: version, - }), - spanProcessors: spanProcessors, - traceExporter, - instrumentations: [ - new HttpInstrumentation({ - enabled: true, - ignoreIncomingRequestHook: (req) => { - const ignoreRoutes = ['/metrics', '/status', '/health', '/healthcheck'] - return ignoreRoutes.some((url) => req.url?.includes(url)) ?? false - }, - startIncomingSpanHook: (req) => { - let tenantId = '' - if (isMultitenant) { - if (requestXForwardedHostRegExp) { - const serverRequest = req - const xForwardedHost = serverRequest.headers['x-forwarded-host'] - if (typeof xForwardedHost !== 'string') return {} - const result = xForwardedHost.match(requestXForwardedHostRegExp) - if (!result) return {} - tenantId = result[1] - } - } else { - tenantId = defaultTenantId - } - - return { - 'tenant.ref': tenantId, - region, - } - }, - headersToSpanAttributes: { - client: { - requestHeaders: requestTraceHeader ? [requestTraceHeader] : [], - }, - server: { - requestHeaders: requestTraceHeader ? [requestTraceHeader] : [], - }, - }, - }), - new ClassInstrumentation({ - targetClass: Storage, - enabled: true, - methodsToInstrument: [ - 'findBucket', - 'listBuckets', - 'createBucket', - 'updateBucket', - 'countObjects', - 'deleteBucket', - 'emptyBucket', - 'healthcheck', - ], - }), - new ClassInstrumentation({ - targetClass: ObjectStorage, - enabled: true, - methodsToInstrument: [ - 'uploadNewObject', - 'uploadOverridingObject', - 'deleteObject', - 'deleteObjects', - 'updateObjectMetadata', - 'updateObjectOwner', - 'findObject', - 'findObjects', - 'copyObject', - 'moveObject', - 'searchObjects', - 'listObjectsV2', - 'signObjectUrl', - 'signObjectUrls', - 'signUploadObjectUrl', - 'verifyObjectSignature', - ], - }), - new ClassInstrumentation({ - targetClass: Uploader, - enabled: true, - methodsToInstrument: ['canUpload', 'prepareUpload', 'upload', 'completeUpload'], - }), - new ClassInstrumentation({ - targetClass: QueueBaseEvent, - enabled: true, - methodsToInstrument: ['send', 'batchSend'], - setName: (name, attrs, eventClass) => { - if (attrs.constructor.name) { - return name + '.' + eventClass.constructor.name - } - return name - }, - }), - new ClassInstrumentation({ - targetClass: S3Backend, - enabled: true, - methodsToInstrument: [ - 'getObject', - 'putObject', - 'deleteObject', - 'listObjects', - 'copyObject', - 'headObject', - 'createMultipartUpload', - 'uploadPart', - 'completeMultipartUpload', - 'abortMultipartUpload', - 'listMultipartUploads', - 'listParts', - 'getSignedUrl', - 'createBucket', - 'deleteBucket', - 'listBuckets', - 'getBucketLocation', - 'getBucketVersioning', - 'putBucketVersioning', - 'getBucketLifecycleConfiguration', - 'putBucketLifecycleConfiguration', - 'deleteBucketLifecycle', - 'uploadObject', - 'privateAssetUrl', - ], - }), - new ClassInstrumentation({ - targetClass: StorageKnexDB, - enabled: true, - methodsToInstrument: ['runQuery'], - setName: (name, attrs) => { - if (attrs.queryName) { - return name + '.' + attrs.queryName - } - return name - }, - setAttributes: { - runQuery: (queryName) => { - return { - queryName, - } - }, - }, - }), - new ClassInstrumentation({ - targetClass: TenantConnection, - enabled: true, - methodsToInstrument: ['transaction', 'setScope'], - }), - new ClassInstrumentation({ - targetClass: S3Store, - enabled: true, - methodsToInstrument: [ - 'write', - 'create', - 'remove', - 'getUpload', - 'declareUploadLength', - 'uploadIncompletePart', - 'uploadPart', - 'downloadIncompletePart', - 'uploadParts', - ], - setName: (name) => 'Tus.' + name, - }), - new ClassInstrumentation({ - targetClass: StreamSplitter, - enabled: true, - methodsToInstrument: ['emitEvent'], - setName: (name: string, attrs: any) => { - if (attrs.event) { - return name + '.' + attrs.event - } - return name - }, - setAttributes: { - emitEvent: function (event) { - return { - part: this.part as any, - event, - } - }, - }, - }), - new ClassInstrumentation({ - targetClass: PgLock, - enabled: true, - methodsToInstrument: ['lock', 'unlock', 'acquireLock'], - }), - new ClassInstrumentation({ - targetClass: Semaphore, - enabled: true, - methodsToInstrument: ['acquire'], - }), - new ClassInstrumentation({ - targetClass: Permit, - enabled: true, - methodsToInstrument: ['release'], - }), - new ClassInstrumentation({ - targetClass: S3Client, - enabled: true, - methodsToInstrument: ['send'], - setAttributes: { - send: (command) => { - return { - operation: command.constructor.name as string, - } - }, - }, - setName: (name, attrs) => 'S3.' + attrs.operation, - }), - new ClassInstrumentation({ - targetClass: Upload, - enabled: true, - methodsToInstrument: [ - 'done', - '__uploadUsingPut', - '__createMultipartUpload', - 'markUploadAsAborted', - ], - }), - getNodeAutoInstrumentations({ - '@opentelemetry/instrumentation-http': { - enabled: false, - }, - '@opentelemetry/instrumentation-fs': { - enabled: false, - }, - '@opentelemetry/instrumentation-aws-sdk': { - enabled: true, - }, - '@opentelemetry/instrumentation-pg': { - enabled: true, - requireParentSpan: true, - }, - '@opentelemetry/instrumentation-fastify': { - enabled: true, - requestHook: (span, req) => { - span.setAttribute('http.method', req.request.method) - span.setAttribute('http.route', req.request.routerPath) - span.setAttribute('tenant.ref', req.request.tenantId) - span.setAttribute('http.operation', req.request.operation) - span.setAttribute('trace.mode', req.request.tracingMode) - }, - }, - '@opentelemetry/instrumentation-knex': { - enabled: true, - }, - }), - ], - }) - - // Initialize the OpenTelemetry Node SDK - sdk.start() - - // Gracefully shutdown the SDK on process exit - process.once('SIGTERM', () => { - logSchema.info(logger, '[Otel] Stopping', { - type: 'otel', - }) - sdk - .shutdown() - .then(() => { - logSchema.info(logger, '[Otel] Exited', { - type: 'otel', - }) - }) - .catch((error) => - logSchema.error(logger, '[Otel] Shutdown error', { - type: 'otel', - error: error, - }) - ) - }) -} diff --git a/src/internal/monitoring/pprof/client-http.ts b/src/internal/monitoring/pprof/client-http.ts new file mode 100644 index 000000000..178413bab --- /dev/null +++ b/src/internal/monitoring/pprof/client-http.ts @@ -0,0 +1,119 @@ +import { Readable } from 'stream' +import type { ReadableStream as NodeReadableStream } from 'stream/web' +import type { PprofRequestTargetType } from './types' + +const PPROF_ERROR_BODY_MAX_BYTES = 4 * 1024 + +type PprofQueryValue = boolean | number | string | undefined + +export function resolvePprofAdminUrl( + baseUrl: string, + requestPath: string, + params?: Record +) { + const url = new URL(baseUrl) + const normalizedBasePath = url.pathname.endsWith('/') ? url.pathname : `${url.pathname}/` + const normalizedRequestPath = requestPath.replace(/^\/+/, '') + + url.hash = '' + url.search = '' + url.pathname = + normalizedBasePath === '/' + ? `/${normalizedRequestPath}` + : `${normalizedBasePath}${normalizedRequestPath}` + + for (const [key, value] of Object.entries(params ?? {})) { + if (value === undefined) { + continue + } + + url.searchParams.set(key, String(value)) + } + + return url.toString() +} + +async function readResponseBody(response: Response) { + if (!response.body) { + return '' + } + + const reader = response.body.getReader() + const chunks: Buffer[] = [] + let remaining = PPROF_ERROR_BODY_MAX_BYTES + let truncated = false + + try { + while (remaining > 0) { + const { done, value } = await reader.read() + if (done) { + break + } + + const chunk = Buffer.from(value) + if (chunk.byteLength <= remaining) { + chunks.push(chunk) + remaining -= chunk.byteLength + continue + } + + chunks.push(chunk.subarray(0, remaining)) + remaining = 0 + truncated = true + } + } finally { + if (truncated) { + await reader.cancel().catch(() => {}) + } + } + + const bodyText = Buffer.concat(chunks).toString('utf8').trim() + if (!bodyText) { + return '' + } + + return truncated ? `: ${bodyText}… [truncated]` : `: ${bodyText}` +} + +export async function fetchPprofStream(options: { + adminUrl: string + apiKey: string + nodeModulesSourceMaps?: string + seconds: number + sourceMaps?: boolean + type: PprofRequestTargetType + workerId?: number +}) { + const response = await fetch( + resolvePprofAdminUrl(options.adminUrl, `/debug/pprof/${options.type}`, { + nodeModulesSourceMaps: options.nodeModulesSourceMaps, + seconds: options.seconds, + sourceMaps: options.sourceMaps, + workerId: options.workerId, + }), + { + headers: { + Accept: 'multipart/mixed', + ApiKey: options.apiKey, + }, + method: 'GET', + } + ) + + if (!response.ok) { + const statusText = response.statusText ? ` ${response.statusText}` : '' + throw new Error( + `Failed to capture pprof profile: HTTP ${response.status}${statusText}${await readResponseBody(response)}` + ) + } + + if (!response.body) { + throw new Error('Pprof capture response did not include a response body.') + } + + return { + contentType: response.headers.get('content-type') ?? undefined, + // Node's Readable.fromWeb expects the stream/web type, while fetch exposes the DOM shape. + stream: Readable.fromWeb(response.body as unknown as NodeReadableStream), + } +} diff --git a/src/internal/monitoring/pprof/client.test.ts b/src/internal/monitoring/pprof/client.test.ts new file mode 100644 index 000000000..7eec35e81 --- /dev/null +++ b/src/internal/monitoring/pprof/client.test.ts @@ -0,0 +1,129 @@ +import { fetchPprofStream, resolvePprofAdminUrl } from './client-http' + +async function readStream(stream: NodeJS.ReadableStream) { + const chunks: Buffer[] = [] + + for await (const chunk of stream as AsyncIterable) { + if (typeof chunk === 'string') { + chunks.push(Buffer.from(chunk)) + continue + } + + chunks.push(Buffer.from(chunk)) + } + + return Buffer.concat(chunks).toString('utf8') +} + +describe('resolvePprofAdminUrl', () => { + it('preserves ADMIN_URL path prefixes when joining absolute-looking paths', () => { + expect( + resolvePprofAdminUrl('https://example.com/admin/internal', '/debug/pprof/profile', { + seconds: 60, + sourceMaps: false, + }) + ).toBe('https://example.com/admin/internal/debug/pprof/profile?seconds=60&sourceMaps=false') + + expect( + resolvePprofAdminUrl('https://example.com/admin/internal/', '/debug/pprof/heap', { + workerId: 7, + }) + ).toBe('https://example.com/admin/internal/debug/pprof/heap?workerId=7') + }) + + it('drops pre-existing query params from ADMIN_URL before adding request params', () => { + expect( + resolvePprofAdminUrl('https://example.com/admin/internal?stale=1', '/debug/pprof/profile', { + seconds: 60, + }) + ).toBe('https://example.com/admin/internal/debug/pprof/profile?seconds=60') + }) +}) + +describe('fetchPprofStream', () => { + afterEach(() => { + vi.restoreAllMocks() + vi.unstubAllGlobals() + }) + + it('requests multipart pprof output with the existing headers and query params', async () => { + const fetchMock = vi.fn().mockResolvedValue( + new Response('profile-data', { + headers: { + 'content-type': 'multipart/mixed; boundary=pprof-test', + }, + }) + ) + vi.stubGlobal('fetch', fetchMock) + + const response = await fetchPprofStream({ + adminUrl: 'https://example.com/admin', + apiKey: 'secret', + nodeModulesSourceMaps: 'next,@next/next-server', + seconds: 90, + sourceMaps: true, + type: 'profile', + workerId: 0, + }) + + expect(fetchMock).toHaveBeenCalledWith( + 'https://example.com/admin/debug/pprof/profile?nodeModulesSourceMaps=next%2C%40next%2Fnext-server&seconds=90&sourceMaps=true&workerId=0', + { + headers: { + Accept: 'multipart/mixed', + ApiKey: 'secret', + }, + method: 'GET', + } + ) + expect(response.contentType).toBe('multipart/mixed; boundary=pprof-test') + expect(await readStream(response.stream)).toBe('profile-data') + }) + + it('surfaces non-2xx responses with the response body', async () => { + const fetchMock = vi.fn().mockResolvedValue( + new Response('upstream failure', { + status: 502, + statusText: 'Bad Gateway', + }) + ) + vi.stubGlobal('fetch', fetchMock) + + await expect( + fetchPprofStream({ + adminUrl: 'https://example.com/admin', + apiKey: 'secret', + seconds: 30, + type: 'heap', + }) + ).rejects.toThrow('Failed to capture pprof profile: HTTP 502 Bad Gateway: upstream failure') + }) + + it('caps verbose non-2xx response bodies', async () => { + const noisyBody = 'x'.repeat(6000) + const fetchMock = vi.fn().mockResolvedValue( + new Response(noisyBody, { + status: 502, + statusText: 'Bad Gateway', + }) + ) + vi.stubGlobal('fetch', fetchMock) + + let error: unknown + + try { + await fetchPprofStream({ + adminUrl: 'https://example.com/admin', + apiKey: 'secret', + seconds: 30, + type: 'heap', + }) + } catch (caught) { + error = caught + } + + expect(error).toBeInstanceOf(Error) + expect((error as Error).message).toContain('… [truncated]') + expect((error as Error).message.length).toBeLessThan(4300) + }) +}) diff --git a/src/internal/monitoring/pprof/download.test.ts b/src/internal/monitoring/pprof/download.test.ts new file mode 100644 index 000000000..3c748a3e3 --- /dev/null +++ b/src/internal/monitoring/pprof/download.test.ts @@ -0,0 +1,462 @@ +import fs from 'fs/promises' +import os from 'os' +import path from 'path' +import { Readable } from 'stream' +import { vi } from 'vitest' +import { writeMultipartPprofToFile } from './download' + +function buildMultipartBody( + boundary: string, + parts: Array<{ + body: Buffer + headers: Record + }>, + options?: { + close?: boolean + } +) { + const chunks: Buffer[] = [] + + for (const part of parts) { + const headerBlock = + `--${boundary}\r\n` + + Object.entries({ + ...part.headers, + 'Content-Length': `${part.body.byteLength}`, + }) + .map(([name, value]) => `${name}: ${value}\r\n`) + .join('') + + '\r\n' + + chunks.push(Buffer.from(headerBlock, 'utf8')) + chunks.push(part.body) + chunks.push(Buffer.from('\r\n', 'utf8')) + } + + if (options?.close !== false) { + chunks.push(Buffer.from(`--${boundary}--\r\n`, 'utf8')) + } + + return Buffer.concat(chunks) +} + +function splitBuffer(buffer: Buffer, chunkSize: number) { + const chunks: Buffer[] = [] + + for (let index = 0; index < buffer.length; index += chunkSize) { + chunks.push(buffer.subarray(index, index + chunkSize)) + } + + return chunks +} + +describe('writeMultipartPprofToFile', () => { + let tempDir: string + + beforeEach(async () => { + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pprof-download-')) + vi.spyOn(console, 'error').mockImplementation(() => {}) + vi.spyOn(console, 'log').mockImplementation(() => {}) + }) + + afterEach(async () => { + vi.restoreAllMocks() + await fs.rm(tempDir, { recursive: true, force: true }) + }) + + it('writes the multipart profile part to disk while ignoring heartbeat parts', async () => { + const boundary = 'pprof-test-boundary' + const profile = Buffer.from([1, 2, 3, 4, 5, 6]) + const outputPath = path.join(tempDir, 'nested', 'profile.pprof') + const body = buildMultipartBody(boundary, [ + { + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: Buffer.from( + JSON.stringify({ + applicationId: 'storage', + event: 'started', + filename: 'storage-cpu.pprof', + seconds: 60, + servingWorkerId: 2, + type: 'cpu', + workerCount: 2, + }), + 'utf8' + ), + }, + { + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: Buffer.from( + JSON.stringify({ + at: '2026-04-17T12:00:00.000Z', + event: 'ping', + }), + 'utf8' + ), + }, + { + headers: { + 'Content-Disposition': 'attachment; filename="storage-cpu.pprof"', + 'Content-Type': 'application/octet-stream', + }, + body: profile, + }, + ]) + + const response = Readable.from(splitBuffer(body, 7)) + + const result = await writeMultipartPprofToFile( + response, + `multipart/mixed; boundary="${boundary}"`, + { + outputPath, + } + ) + + await expect(fs.readFile(outputPath)).resolves.toEqual(profile) + expect(result.outputPath).toBe(outputPath) + expect(result.startedEvent).toMatchObject({ + applicationId: 'storage', + event: 'started', + servingWorkerId: 2, + type: 'cpu', + }) + }) + + it('rejects when the server streams an error part', async () => { + const boundary = 'pprof-test-boundary' + const body = buildMultipartBody(boundary, [ + { + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: Buffer.from( + JSON.stringify({ + applicationId: 'storage', + event: 'started', + filename: 'storage-heap.pprof', + seconds: 10, + servingWorkerId: 2, + type: 'heap', + workerId: 7, + }), + 'utf8' + ), + }, + { + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: Buffer.from( + JSON.stringify({ + event: 'error', + error: { + code: 'PLT_CTR_FAILED_TO_STOP_PROFILING', + message: 'stop failed', + statusCode: 502, + }, + }), + 'utf8' + ), + }, + ]) + + const response = Readable.from(splitBuffer(body, 11)) + + await expect( + writeMultipartPprofToFile(response, `multipart/mixed; boundary=${boundary}`, { + outputPath: path.join(tempDir, 'profile.pprof'), + }) + ).rejects.toThrow('[PLT_CTR_FAILED_TO_STOP_PROFILING] stop failed') + }) + + it('sanitizes server-provided filenames before writing the profile to dist', async () => { + const boundary = 'pprof-test-boundary' + const profile = Buffer.from([7, 8, 9]) + vi.spyOn(process, 'cwd').mockReturnValue(tempDir) + + const body = buildMultipartBody(boundary, [ + { + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: Buffer.from( + JSON.stringify({ + applicationId: 'storage', + event: 'started', + filename: 'storage-cpu.pprof', + seconds: 60, + servingWorkerId: 2, + type: 'cpu', + workerCount: 2, + }), + 'utf8' + ), + }, + { + headers: { + 'Content-Disposition': 'attachment; filename="..\\\\..\\\\evil.pprof"', + 'Content-Type': 'application/octet-stream', + }, + body: profile, + }, + ]) + + const result = await writeMultipartPprofToFile( + Readable.from(splitBuffer(body, 9)), + `multipart/mixed; boundary=${boundary}` + ) + + const expectedOutputPath = path.join(tempDir, 'dist', 'evil.pprof') + expect(result.outputPath).toBe(expectedOutputPath) + await expect(fs.readFile(expectedOutputPath)).resolves.toEqual(profile) + }) + + it('falls back to quoted filenames when extended filename decoding fails', async () => { + const boundary = 'pprof-test-boundary' + const profile = Buffer.from([10, 11, 12]) + vi.spyOn(process, 'cwd').mockReturnValue(tempDir) + + const body = buildMultipartBody(boundary, [ + { + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: Buffer.from( + JSON.stringify({ + applicationId: 'storage', + event: 'started', + filename: 'started-fallback.pprof', + seconds: 30, + servingWorkerId: 2, + type: 'cpu', + workerCount: 2, + }), + 'utf8' + ), + }, + { + headers: { + 'Content-Disposition': + 'attachment; filename*=UTF-8\'\'broken%ZZ; filename="quoted.pprof"', + 'Content-Type': 'application/octet-stream', + }, + body: profile, + }, + ]) + + const result = await writeMultipartPprofToFile( + Readable.from(splitBuffer(body, 9)), + `multipart/mixed; boundary=${boundary}` + ) + + const expectedOutputPath = path.join(tempDir, 'dist', 'quoted.pprof') + expect(result.outputPath).toBe(expectedOutputPath) + await expect(fs.readFile(expectedOutputPath)).resolves.toEqual(profile) + }) + + it('surfaces UND_ERR_SOCKET disconnects with a clearer capture error', async () => { + const boundary = 'pprof-test-boundary' + const body = buildMultipartBody( + boundary, + [ + { + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: Buffer.from( + JSON.stringify({ + applicationId: 'storage', + event: 'started', + filename: 'storage-cpu.pprof', + seconds: 300, + servingWorkerId: 2, + type: 'cpu', + }), + 'utf8' + ), + }, + { + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: Buffer.from( + JSON.stringify({ + at: '2026-04-17T19:04:26.750Z', + event: 'ping', + }), + 'utf8' + ), + }, + ], + { + close: false, + } + ) + + const cause = Object.assign(new Error('upstream transport lost'), { + code: 'UND_ERR_SOCKET', + }) + const disconnectedError = new Error('stream aborted', { cause }) + const response = Readable.from( + (async function* () { + yield body + throw disconnectedError + })() + ) + + let error: unknown + + try { + await writeMultipartPprofToFile(response, `multipart/mixed; boundary=${boundary}`, { + outputPath: path.join(tempDir, 'profile.pprof'), + }) + } catch (caught) { + error = caught + } + + expect(error).toBeInstanceOf(Error) + expect((error as Error).message).toContain( + 'Pprof capture stream ended before the profile was delivered for storage (cpu, 300s).' + ) + expect((error as Error).message).toContain( + 'Last heartbeat arrived at 2026-04-17T19:04:26.750Z.' + ) + expect((error as Error).message).toContain('Serving worker: 2.') + expect((error as Error).message).toContain('The connection died mid-capture') + expect((error as Error).message).toContain('load balancer') + expect((error as Error).message).toContain('serving worker exited') + expect((error as Error).cause).toBe(disconnectedError) + }) + + it('surfaces the same diagnostic on clean EOF after capture start', async () => { + const boundary = 'pprof-test-boundary' + const body = buildMultipartBody( + boundary, + [ + { + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: Buffer.from( + JSON.stringify({ + applicationId: 'storage', + event: 'started', + filename: 'storage-heap.pprof', + seconds: 120, + servingWorkerId: 2, + type: 'heap', + }), + 'utf8' + ), + }, + { + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: Buffer.from( + JSON.stringify({ + at: '2026-04-17T20:00:00.000Z', + event: 'ping', + }), + 'utf8' + ), + }, + ], + { + close: false, + } + ) + + let error: unknown + + try { + await writeMultipartPprofToFile( + Readable.from(splitBuffer(body, 13)), + `multipart/mixed; boundary=${boundary}`, + { + outputPath: path.join(tempDir, 'profile.pprof'), + } + ) + } catch (caught) { + error = caught + } + + expect(error).toBeInstanceOf(Error) + expect((error as Error).message).toContain( + 'Pprof capture stream ended before the profile was delivered for storage (heap, 120s).' + ) + expect((error as Error).message).toContain( + 'Last heartbeat arrived at 2026-04-17T20:00:00.000Z.' + ) + expect((error as Error).message).toContain('Serving worker: 2.') + expect((error as Error).cause).toBeUndefined() + }) + + it('rethrows terminated stream errors before the capture starts', async () => { + const boundary = 'pprof-test-boundary' + const terminatedError = new TypeError('terminated') + const response = Readable.from( + (async function* () { + throw terminatedError + })() + ) + + await expect( + writeMultipartPprofToFile(response, `multipart/mixed; boundary=${boundary}`, { + outputPath: path.join(tempDir, 'profile.pprof'), + }) + ).rejects.toBe(terminatedError) + }) + + it('surfaces an exact undici terminated error after capture start', async () => { + const boundary = 'pprof-test-boundary' + const body = buildMultipartBody( + boundary, + [ + { + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: Buffer.from( + JSON.stringify({ + applicationId: 'storage', + event: 'started', + filename: 'storage-cpu.pprof', + seconds: 45, + servingWorkerId: 2, + type: 'cpu', + }), + 'utf8' + ), + }, + ], + { + close: false, + } + ) + + const terminatedError = new TypeError('terminated') + const response = Readable.from( + (async function* () { + yield body + throw terminatedError + })() + ) + + await expect( + writeMultipartPprofToFile(response, `multipart/mixed; boundary=${boundary}`, { + outputPath: path.join(tempDir, 'profile.pprof'), + }) + ).rejects.toMatchObject({ + cause: terminatedError, + message: expect.stringContaining( + 'Pprof capture stream ended before the profile was delivered for storage (cpu, 45s).' + ), + }) + }) +}) diff --git a/src/internal/monitoring/pprof/download.ts b/src/internal/monitoring/pprof/download.ts new file mode 100644 index 000000000..0d8db414d --- /dev/null +++ b/src/internal/monitoring/pprof/download.ts @@ -0,0 +1,450 @@ +import fs from 'fs' +import { mkdir } from 'fs/promises' +import path from 'path' + +export interface MultipartPprofStartedEvent { + applicationId: string + event: 'started' + filename: string + seconds: number + servingWorkerId?: number + type: 'cpu' | 'heap' + workerCount?: number + workerId?: number +} + +export interface MultipartPprofPingEvent { + at: string + event: 'ping' +} + +export interface MultipartPprofErrorEvent { + error: { + code?: string + message: string + statusCode: number + } + event: 'error' +} + +export type MultipartPprofJsonEvent = + | MultipartPprofStartedEvent + | MultipartPprofPingEvent + | MultipartPprofErrorEvent + +type MultipartHeaders = Record + +enum MultipartState { + Body = 'body', + Boundary = 'boundary', + Done = 'done', + Headers = 'headers', +} + +const DEFAULT_PPROF_FILENAME = 'profile.pprof' + +export function extractMultipartBoundary(contentType: string | undefined) { + if (!contentType) { + return + } + + const match = contentType.match(/boundary="?([^";]+)"?/i) + return match?.[1] +} + +function parseMultipartHeaders(value: string) { + const headers: MultipartHeaders = {} + + for (const line of value.split('\r\n')) { + const separatorIndex = line.indexOf(':') + if (separatorIndex < 0) { + continue + } + + const name = line.slice(0, separatorIndex).trim().toLowerCase() + const headerValue = line.slice(separatorIndex + 1).trim() + if (name) { + headers[name] = headerValue + } + } + + return headers +} + +function parseMultipartContentLength(headers: MultipartHeaders) { + const value = headers['content-length'] + if (!value) { + throw new Error('Multipart pprof response is missing a Content-Length part header.') + } + + const parsed = Number.parseInt(value, 10) + if (!Number.isSafeInteger(parsed) || parsed < 0) { + throw new Error(`Invalid multipart Content-Length header: ${value}`) + } + + return parsed +} + +function parseMultipartFilename(headers: MultipartHeaders) { + const disposition = headers['content-disposition'] + if (!disposition) { + return + } + + const extendedMatch = disposition.match(/filename\*=UTF-8''([^;]+)/i) + if (extendedMatch?.[1]) { + try { + return decodeURIComponent(extendedMatch[1]) + } catch {} + } + + const quotedMatch = disposition.match(/filename="([^"]+)"/i) + if (quotedMatch?.[1]) { + return quotedMatch[1] + } + + const plainMatch = disposition.match(/filename=([^;]+)/i) + return plainMatch?.[1]?.trim() +} + +function sanitizeMultipartFilename(filename: string) { + const sanitized = path.posix.basename(filename.trim().replaceAll('\\', '/')) + + if (!sanitized || sanitized === '.' || sanitized === '..') { + return DEFAULT_PPROF_FILENAME + } + + return sanitized +} + +async function openOutputFile(filePath: string) { + await mkdir(path.dirname(filePath), { recursive: true }) + return fs.createWriteStream(filePath) +} + +async function closeOutputFile(stream: fs.WriteStream | undefined) { + if (!stream) { + return + } + + await new Promise((resolve, reject) => { + stream.once('finish', resolve) + stream.once('error', reject) + stream.end() + }) +} + +function getErrorCause(error: unknown) { + if (error && typeof error === 'object' && 'cause' in error) { + return (error as { cause?: unknown }).cause + } + + return undefined +} + +function getErrorCode(error: unknown) { + if (error && typeof error === 'object' && 'code' in error) { + const code = (error as { code?: unknown }).code + return typeof code === 'string' ? code : undefined + } + + return undefined +} + +function getErrorMessage(error: unknown) { + return error instanceof Error ? error.message : undefined +} + +function isTerminatedMultipartStreamError(error: unknown): boolean { + const cause = getErrorCause(error) + + if (getErrorCode(cause) === 'UND_ERR_SOCKET') { + return true + } + + if (error instanceof TypeError && error.message === 'terminated') { + return true + } + + const causeMessage = getErrorMessage(cause)?.toLowerCase() + if (causeMessage?.includes('other side closed') || causeMessage?.includes('socket closed')) { + return true + } + + return false +} + +function buildServingWorkerDetail(servingWorkerId: number | undefined) { + return servingWorkerId === undefined ? '' : ` Serving worker: ${servingWorkerId}.` +} + +function buildInterruptedCaptureMessage(options: { + lastHeartbeatAt?: string + startedEvent: MultipartPprofStartedEvent +}) { + const captureLabel = + `${options.startedEvent.applicationId} ` + + `(${options.startedEvent.type}, ${options.startedEvent.seconds}s)` + const lastHeartbeatDetail = options.lastHeartbeatAt + ? ` Last heartbeat arrived at ${options.lastHeartbeatAt}.` + : '' + const servingWorkerDetail = buildServingWorkerDetail(options.startedEvent.servingWorkerId) + + return ( + `Pprof capture stream ended before the profile was delivered for ${captureLabel}.` + + lastHeartbeatDetail + + servingWorkerDetail + + ' The connection died mid-capture, usually because a load balancer ' + + 'closed the long-running response or the serving worker exited. ' + + 'Use a shorter capture.' + ) +} + +function createInterruptedCaptureError( + options: { + lastHeartbeatAt?: string + startedEvent: MultipartPprofStartedEvent + }, + cause?: unknown +) { + return cause === undefined + ? new Error(buildInterruptedCaptureMessage(options)) + : new Error(buildInterruptedCaptureMessage(options), { cause }) +} + +export async function writeMultipartPprofToFile( + stream: NodeJS.ReadableStream, + contentType: string | undefined, + options?: { + outputPath?: string + } +) { + const boundary = extractMultipartBoundary(contentType) + if (!boundary) { + throw new Error('Expected a multipart/mixed pprof response with a boundary parameter.') + } + + const boundaryLine = `--${boundary}` + const parserState = { value: MultipartState.Boundary as MultipartState } + let buffer = Buffer.alloc(0) + let currentHeaders: MultipartHeaders = {} + let currentLength = 0 + let jsonChunks: Buffer[] = [] + let outputFile: fs.WriteStream | undefined + let outputPath = options?.outputPath ? path.resolve(options.outputPath) : undefined + let receivedProfile = false + let startedEvent: MultipartPprofStartedEvent | undefined + let lastHeartbeatAt: string | undefined + + const emitJsonPart = async () => { + const payload = JSON.parse( + Buffer.concat(jsonChunks).toString('utf8') + ) as MultipartPprofJsonEvent + jsonChunks = [] + + if (payload.event === 'started') { + startedEvent = payload + const servingWorkerDetail = buildServingWorkerDetail(payload.servingWorkerId) + console.log( + `Capture started for ${payload.applicationId} (${payload.type}, ${payload.seconds}s).${servingWorkerDetail}` + ) + return + } + + if (payload.event === 'ping') { + lastHeartbeatAt = payload.at + console.log(`Capture still running at ${payload.at}.`) + return + } + + throw new Error( + payload.error.code + ? `[${payload.error.code}] ${payload.error.message}` + : payload.error.message + ) + } + + const openProfileOutput = async () => { + if (outputFile) { + return outputFile + } + + const filename = sanitizeMultipartFilename( + parseMultipartFilename(currentHeaders) ?? startedEvent?.filename ?? DEFAULT_PPROF_FILENAME + ) + + outputPath ??= path.resolve(process.cwd(), 'dist', filename) + outputFile = await openOutputFile(outputPath) + return outputFile + } + + const consumeBoundaryLine = () => { + const lineEnd = buffer.indexOf('\r\n') + if (lineEnd < 0) { + return false + } + + const line = buffer.subarray(0, lineEnd).toString('latin1') + buffer = buffer.subarray(lineEnd + 2) + + if (line === `${boundaryLine}--`) { + parserState.value = MultipartState.Done + return true + } + + if (line === boundaryLine) { + parserState.value = MultipartState.Headers + return true + } + + throw new Error(`Unexpected multipart boundary line: ${line}`) + } + + const consumeHeaders = () => { + const headersEnd = buffer.indexOf('\r\n\r\n') + if (headersEnd < 0) { + return false + } + + currentHeaders = parseMultipartHeaders(buffer.subarray(0, headersEnd).toString('latin1')) + currentLength = parseMultipartContentLength(currentHeaders) + buffer = buffer.subarray(headersEnd + 4) + parserState.value = MultipartState.Body + return true + } + + const consumeJsonBody = async () => { + if (buffer.length < currentLength + 2) { + return false + } + + jsonChunks.push(buffer.subarray(0, currentLength)) + + if (buffer.subarray(currentLength, currentLength + 2).toString('latin1') !== '\r\n') { + throw new Error('Malformed multipart pprof response: missing CRLF after JSON part body.') + } + + buffer = buffer.subarray(currentLength + 2) + await emitJsonPart() + parserState.value = MultipartState.Boundary + return true + } + + const consumeProfileBody = async () => { + const file = await openProfileOutput() + const bytesToWrite = Math.min(currentLength, buffer.length) + + if (bytesToWrite > 0) { + const chunk = buffer.subarray(0, bytesToWrite) + buffer = buffer.subarray(bytesToWrite) + currentLength -= bytesToWrite + + if (!file.write(chunk)) { + await new Promise((resolve, reject) => { + file.once('drain', resolve) + file.once('error', reject) + }) + } + } + + if (currentLength > 0) { + return false + } + + if (buffer.length < 2) { + return false + } + + if (buffer.subarray(0, 2).toString('latin1') !== '\r\n') { + throw new Error('Malformed multipart pprof response: missing CRLF after binary profile part.') + } + + buffer = buffer.subarray(2) + receivedProfile = true + parserState.value = MultipartState.Boundary + return true + } + + try { + try { + for await (const chunk of stream) { + buffer = + buffer.length === 0 + ? Buffer.from(chunk as Uint8Array) + : Buffer.concat([buffer, chunk as Uint8Array]) + + let shouldContinueParsing = true + while (shouldContinueParsing) { + const currentState = parserState.value + + if (currentState === MultipartState.Done) { + break + } + + if (currentState === MultipartState.Boundary) { + if (!consumeBoundaryLine()) { + shouldContinueParsing = false + } + continue + } + + if (currentState === MultipartState.Headers) { + if (!consumeHeaders()) { + shouldContinueParsing = false + } + continue + } + + const contentTypeHeader = currentHeaders['content-type']?.toLowerCase() ?? '' + const consumed = contentTypeHeader.startsWith('application/json') + ? await consumeJsonBody() + : await consumeProfileBody() + + if (!consumed) { + shouldContinueParsing = false + } + } + + if (parserState.value === MultipartState.Done) { + break + } + } + } catch (error) { + if (startedEvent && !receivedProfile && isTerminatedMultipartStreamError(error)) { + throw createInterruptedCaptureError( + { + lastHeartbeatAt, + startedEvent, + }, + error + ) + } + + throw error + } + } finally { + await closeOutputFile(outputFile) + } + + if (parserState.value !== MultipartState.Done) { + if (startedEvent && !receivedProfile) { + throw createInterruptedCaptureError({ + lastHeartbeatAt, + startedEvent, + }) + } + + throw new Error('Multipart pprof response ended before the closing boundary.') + } + + if (!receivedProfile || !outputPath) { + throw new Error('Multipart pprof response completed without a profile part.') + } + + console.log(`Saved pprof profile to ${outputPath}.`) + + return { + outputPath, + startedEvent, + } +} diff --git a/src/internal/monitoring/pprof/flame.test.ts b/src/internal/monitoring/pprof/flame.test.ts new file mode 100644 index 000000000..3ad5a7722 --- /dev/null +++ b/src/internal/monitoring/pprof/flame.test.ts @@ -0,0 +1,92 @@ +import { EventEmitter } from 'node:events' +import { vi } from 'vitest' + +const spawnMock = vi.hoisted(() => vi.fn()) + +vi.mock('node:child_process', () => ({ + spawn: spawnMock, +})) + +import { + buildFlameGenerateArgs, + generateFlameArtifacts, + getFlameCommand, + normalizeFlameEnvironment, + resolveFlameMdFormat, +} from './flame' + +function createFakeChild() { + return new EventEmitter() as EventEmitter & { + once: EventEmitter['once'] + } +} + +describe('flame helpers', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('aliases FLAME_SOURCEMAPS_DIRS to FLAME_SOURCEMAP_DIRS', () => { + expect( + normalizeFlameEnvironment({ + FLAME_SOURCEMAPS_DIRS: 'dist', + }) + ).toMatchObject({ + FLAME_SOURCEMAP_DIRS: 'dist', + FLAME_SOURCEMAPS_DIRS: 'dist', + }) + }) + + it('keeps an explicit FLAME_SOURCEMAP_DIRS value', () => { + expect( + normalizeFlameEnvironment({ + FLAME_SOURCEMAP_DIRS: 'build', + FLAME_SOURCEMAPS_DIRS: 'dist', + }) + ).toMatchObject({ + FLAME_SOURCEMAP_DIRS: 'build', + FLAME_SOURCEMAPS_DIRS: 'dist', + }) + }) + + it('builds flame generate args with an optional markdown format', () => { + expect(buildFlameGenerateArgs('/tmp/profile.pprof')).toEqual(['generate', '/tmp/profile.pprof']) + + expect(buildFlameGenerateArgs('/tmp/profile.pprof', 'detailed')).toEqual([ + 'generate', + '--md-format=detailed', + '/tmp/profile.pprof', + ]) + }) + + it('rejects unsupported markdown formats', () => { + expect(() => resolveFlameMdFormat('verbose')).toThrow('Invalid PPROF_FLAME_MD_FORMAT') + }) + + it('spawns flame generate with inherited stdio', async () => { + const child = createFakeChild() + spawnMock.mockReturnValue(child) + + const generationPromise = generateFlameArtifacts('/tmp/profile.pprof', { + env: { + FLAME_SOURCEMAPS_DIRS: 'dist', + }, + mdFormat: 'summary', + }) + + expect(spawnMock).toHaveBeenCalledWith( + getFlameCommand(), + ['generate', '--md-format=summary', '/tmp/profile.pprof'], + { + env: expect.objectContaining({ + FLAME_SOURCEMAP_DIRS: 'dist', + FLAME_SOURCEMAPS_DIRS: 'dist', + }), + stdio: 'inherit', + } + ) + + child.emit('exit', 0, null) + await expect(generationPromise).resolves.toBeUndefined() + }) +}) diff --git a/src/internal/monitoring/pprof/flame.ts b/src/internal/monitoring/pprof/flame.ts new file mode 100644 index 000000000..9642835b0 --- /dev/null +++ b/src/internal/monitoring/pprof/flame.ts @@ -0,0 +1,80 @@ +import { spawn } from 'node:child_process' +import path from 'node:path' + +const FLAME_MD_FORMATS = new Set(['summary', 'detailed', 'adaptive']) + +export function normalizeFlameEnvironment(env: NodeJS.ProcessEnv) { + const nextEnv = { ...env } + + if (nextEnv.FLAME_SOURCEMAPS_DIRS && !nextEnv.FLAME_SOURCEMAP_DIRS) { + nextEnv.FLAME_SOURCEMAP_DIRS = nextEnv.FLAME_SOURCEMAPS_DIRS + } + + return nextEnv +} + +export function resolveFlameMdFormat(value: string | undefined) { + if (!value) { + return + } + + const normalized = value.trim().toLowerCase() + if (!FLAME_MD_FORMATS.has(normalized)) { + throw new Error(`Invalid PPROF_FLAME_MD_FORMAT: ${value}`) + } + + return normalized +} + +export function getFlameCommand() { + return path.resolve( + process.cwd(), + 'node_modules', + '.bin', + process.platform === 'win32' ? 'flame.cmd' : 'flame' + ) +} + +export function buildFlameGenerateArgs(profilePath: string, mdFormat?: string) { + const args = ['generate'] + + if (mdFormat) { + args.push(`--md-format=${mdFormat}`) + } + + args.push(profilePath) + return args +} + +export async function generateFlameArtifacts( + profilePath: string, + options?: { + env?: NodeJS.ProcessEnv + mdFormat?: string + } +) { + const env = normalizeFlameEnvironment(options?.env ?? process.env) + const args = buildFlameGenerateArgs(profilePath, options?.mdFormat) + + await new Promise((resolve, reject) => { + const child = spawn(getFlameCommand(), args, { + env, + stdio: 'inherit', + }) + + child.once('error', reject) + child.once('exit', (code, signal) => { + if (code === 0) { + resolve() + return + } + + if (signal) { + reject(new Error(`@platformatic/flame exited via signal ${signal}`)) + return + } + + reject(new Error(`@platformatic/flame exited with status ${code ?? 'unknown'}`)) + }) + }) +} diff --git a/src/internal/monitoring/pprof/multipart.test.ts b/src/internal/monitoring/pprof/multipart.test.ts new file mode 100644 index 000000000..93a6da824 --- /dev/null +++ b/src/internal/monitoring/pprof/multipart.test.ts @@ -0,0 +1,79 @@ +import type { FastifyReply } from 'fastify' +import { vi } from 'vitest' + +const waitMock = vi.hoisted(() => vi.fn()) + +vi.mock('node:timers/promises', () => ({ + setTimeout: waitMock, +})) + +import { waitForMultipartPprofWindow } from './multipart' +import type { MultipartPprofWriter } from './types' + +function createReply() { + const reply = { + raw: { + destroyed: false, + end: vi.fn(), + socket: { + setKeepAlive: vi.fn(), + }, + writableEnded: false, + write: vi.fn(), + writeHead: vi.fn(), + }, + } + + return reply +} + +describe('waitForMultipartPprofWindow', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('stops sending heartbeat parts after the first write failure', async () => { + const reply = createReply() + const writer: MultipartPprofWriter = { + boundary: 'pprof-test', + close: vi.fn(), + writeBinaryPart: vi.fn(), + writeJsonPart: vi.fn().mockReturnValue(false), + } + let keepAliveCallback: (() => void) | undefined + let cleared = false + const clearIntervalSpy = vi.spyOn(globalThis, 'clearInterval').mockImplementation(() => { + cleared = true + }) + + vi.spyOn(globalThis, 'setInterval').mockImplementation(((callback: () => void) => { + keepAliveCallback = callback + return { + unref: vi.fn(), + } as unknown as NodeJS.Timeout + }) as typeof setInterval) + + waitMock.mockImplementation(async () => { + keepAliveCallback?.() + + if (!cleared) { + keepAliveCallback?.() + } + }) + + await waitForMultipartPprofWindow( + reply as unknown as FastifyReply, + writer, + 6, + new AbortController().signal + ) + + expect(reply.raw.socket.setKeepAlive).toHaveBeenCalledWith(true, 5000) + expect(writer.writeJsonPart).toHaveBeenCalledTimes(1) + expect(clearIntervalSpy).toHaveBeenCalled() + }) +}) diff --git a/src/internal/monitoring/pprof/multipart.ts b/src/internal/monitoring/pprof/multipart.ts new file mode 100644 index 000000000..f9c570618 --- /dev/null +++ b/src/internal/monitoring/pprof/multipart.ts @@ -0,0 +1,142 @@ +import { randomUUID } from 'node:crypto' +import { setTimeout as wait } from 'node:timers/promises' +import type { FastifyReply } from 'fastify' +import { + buildPprofFilename, + buildPprofResponseHeaders, + resolvePprofFilenameTarget, +} from './runtime' +import type { MultipartPprofWriter, PprofCaptureType, WattPprofSelection } from './types' + +const PPROF_MULTIPART_PING_INTERVAL_MS = 5000 +const PPROF_MULTIPART_BOUNDARY_PREFIX = 'pprof' +const PPROF_SOCKET_KEEPALIVE_INITIAL_DELAY_MS = 5000 + +function canWriteMultipartPprof(reply: FastifyReply) { + return !reply.raw.destroyed && !reply.raw.writableEnded +} + +function writeMultipartPprofPart( + reply: FastifyReply, + boundary: string, + headers: Record, + body: Buffer +) { + if (!canWriteMultipartPprof(reply)) { + return false + } + + const headerBlock = + `--${boundary}\r\n` + + Object.entries({ + ...headers, + 'Content-Length': `${body.byteLength}`, + }) + .map(([name, value]) => `${name}: ${value}\r\n`) + .join('') + + '\r\n' + + try { + reply.raw.write(headerBlock) + reply.raw.write(body) + reply.raw.write('\r\n') + return true + } catch { + return false + } +} + +export function createMultipartPprofWriter( + reply: FastifyReply, + selection: WattPprofSelection, + type: PprofCaptureType, + seconds: number +): MultipartPprofWriter { + const boundary = `${PPROF_MULTIPART_BOUNDARY_PREFIX}-${randomUUID()}` + const filename = buildPprofFilename(resolvePprofFilenameTarget(selection), type) + + reply.hijack() + reply.raw.writeHead( + 200, + buildPprofResponseHeaders(selection, `multipart/mixed; boundary=${boundary}`) + ) + + const writer: MultipartPprofWriter = { + boundary, + close() { + if (!canWriteMultipartPprof(reply)) { + return + } + + try { + reply.raw.end(`--${boundary}--\r\n`) + } catch {} + }, + writeBinaryPart(headers, body) { + return writeMultipartPprofPart(reply, boundary, headers, body) + }, + writeJsonPart(payload) { + return writeMultipartPprofPart( + reply, + boundary, + { + 'Content-Type': 'application/json; charset=utf-8', + }, + Buffer.from(JSON.stringify(payload), 'utf8') + ) + }, + } + + writer.writeJsonPart({ + applicationId: selection.applicationId, + event: 'started', + filename, + seconds, + ...(selection.servingWorkerId !== undefined + ? { servingWorkerId: selection.servingWorkerId } + : {}), + type, + ...(selection.requestedWorkerId !== undefined + ? { workerId: selection.requestedWorkerId } + : { workerCount: selection.targets.length }), + }) + + return writer +} + +function enablePprofSocketKeepAlive(reply: FastifyReply) { + if (reply.raw.destroyed || reply.raw.writableEnded) { + return + } + + if (typeof reply.raw.socket?.setKeepAlive === 'function') { + reply.raw.socket.setKeepAlive(true, PPROF_SOCKET_KEEPALIVE_INITIAL_DELAY_MS) + } +} + +export async function waitForMultipartPprofWindow( + reply: FastifyReply, + writer: MultipartPprofWriter, + seconds: number, + signal: AbortSignal +) { + enablePprofSocketKeepAlive(reply) + + const keepAliveInterval = setInterval(() => { + const wrote = writer.writeJsonPart({ + at: new Date().toISOString(), + event: 'ping', + }) + + if (!wrote) { + clearInterval(keepAliveInterval) + } + }, PPROF_MULTIPART_PING_INTERVAL_MS) + keepAliveInterval.unref() + + try { + await wait(seconds * 1000, undefined, { signal }) + } finally { + clearInterval(keepAliveInterval) + } +} diff --git a/src/internal/monitoring/pprof/profile.test.ts b/src/internal/monitoring/pprof/profile.test.ts new file mode 100644 index 000000000..ec915f965 --- /dev/null +++ b/src/internal/monitoring/pprof/profile.test.ts @@ -0,0 +1,260 @@ +import { + Function as PprofFunction, + Line as PprofLine, + Location as PprofLocation, + Mapping as PprofMapping, + Sample as PprofSample, + ValueType as PprofValueType, + Profile, + StringTable, +} from 'pprof-format' +import { mergeProfiles, mergeStoppedProfileBuffers } from './profile' + +function toArrayBuffer(value: Uint8Array) { + const copy = new Uint8Array(value.byteLength) + copy.set(value) + return copy.buffer +} + +function buildProfile(options: { + functionName: string + sampleValue: number + comment?: string + defaultSampleTypeName?: string + omitTiming?: boolean + locationMappingId?: number + lineFunctionId?: number + locationId?: number + locationIdsInSample?: number[] + periodTypeName?: string + sampleTypeName?: string + timeNanos?: bigint + durationNanos?: bigint +}) { + const stringTable = new StringTable() + const sampleTypeName = options.sampleTypeName ?? 'samples' + const periodTypeName = options.periodTypeName ?? sampleTypeName + const defaultSampleTypeName = options.defaultSampleTypeName ?? sampleTypeName + const sampleType = new PprofValueType({ + type: stringTable.dedup(sampleTypeName), + unit: stringTable.dedup('count'), + }) + const periodType = new PprofValueType({ + type: stringTable.dedup(periodTypeName), + unit: stringTable.dedup('count'), + }) + const locationId = options.locationId ?? 1 + + return new Profile({ + stringTable, + sampleType: [sampleType], + periodType, + period: 1, + ...(options.omitTiming + ? {} + : { + timeNanos: options.timeNanos ?? 1n, + durationNanos: options.durationNanos ?? 1_000_000_000n, + }), + mapping: [ + new PprofMapping({ + id: 1, + hasFunctions: true, + hasFilenames: true, + hasLineNumbers: true, + }), + ], + function: [ + new PprofFunction({ + id: 1, + name: stringTable.dedup(options.functionName), + systemName: stringTable.dedup(options.functionName), + filename: stringTable.dedup(`${options.functionName}.ts`), + startLine: 1, + }), + ], + location: [ + new PprofLocation({ + id: locationId, + mappingId: options.locationMappingId ?? 1, + line: [new PprofLine({ functionId: options.lineFunctionId ?? 1, line: 1 })], + }), + ], + sample: [ + new PprofSample({ + locationId: options.locationIdsInSample ?? [locationId], + value: [options.sampleValue], + }), + ], + defaultSampleType: stringTable.dedup(defaultSampleTypeName), + comment: options.comment ? [stringTable.dedup(options.comment)] : [], + }) +} + +describe('mergeProfiles', () => { + it('merges compatible profiles and preserves the latest timing metadata', () => { + const merged = mergeProfiles([ + buildProfile({ + functionName: 'workerA', + sampleValue: 3, + comment: 'shared comment', + timeNanos: 1n, + durationNanos: 2n, + }), + buildProfile({ + functionName: 'workerB', + sampleValue: 5, + comment: 'shared comment', + timeNanos: 9n, + durationNanos: 7n, + }), + ]) + + const decoded = Profile.decode(merged) + expect(decoded.sample.map((sample) => sample.value[0])).toEqual([3, 5]) + expect(decoded.function).toHaveLength(2) + expect(decoded.comment).toHaveLength(1) + expect(decoded.timeNanos).toBe(9) + expect(decoded.durationNanos).toBe(7) + }) + + it('treats missing timing metadata as zero when picking the latest profile timing', () => { + const merged = mergeProfiles([ + buildProfile({ + functionName: 'workerA', + sampleValue: 3, + omitTiming: true, + }), + buildProfile({ + functionName: 'workerB', + sampleValue: 5, + timeNanos: 9n, + durationNanos: 7n, + }), + ]) + + const decoded = Profile.decode(merged) + expect(decoded.timeNanos).toBe(9) + expect(decoded.durationNanos).toBe(7) + }) + + it('rejects profiles with mismatched sample types', () => { + expect(() => + mergeProfiles([ + buildProfile({ functionName: 'workerA', sampleValue: 1, sampleTypeName: 'samples' }), + buildProfile({ functionName: 'workerB', sampleValue: 2, sampleTypeName: 'bytes' }), + ]) + ).toThrow('Cannot merge pprof profiles with different sample types.') + }) + + it('rejects profiles with mismatched period types', () => { + expect(() => + mergeProfiles([ + buildProfile({ functionName: 'workerA', sampleValue: 1, periodTypeName: 'samples' }), + buildProfile({ functionName: 'workerB', sampleValue: 2, periodTypeName: 'bytes' }), + ]) + ).toThrow('Cannot merge pprof profiles with different period types.') + }) + + it('rejects profiles with mismatched default sample types', () => { + expect(() => + mergeProfiles([ + buildProfile({ + functionName: 'workerA', + sampleValue: 1, + defaultSampleTypeName: 'samples', + }), + buildProfile({ + functionName: 'workerB', + sampleValue: 2, + defaultSampleTypeName: 'bytes', + }), + ]) + ).toThrow('Cannot merge pprof profiles with different default sample types.') + }) + + it('rejects profiles whose samples reference unknown location ids', () => { + expect(() => + mergeProfiles([ + buildProfile({ functionName: 'workerA', sampleValue: 1 }), + buildProfile({ + functionName: 'workerB', + sampleValue: 2, + locationIdsInSample: [99], + }), + ]) + ).toThrow('Cannot merge pprof profiles with unknown location id: 99.') + }) + + it('rejects profiles whose samples use zero location ids', () => { + expect(() => + mergeProfiles([ + buildProfile({ functionName: 'workerA', sampleValue: 1 }), + buildProfile({ + functionName: 'workerB', + sampleValue: 2, + locationIdsInSample: [0], + }), + ]) + ).toThrow('Cannot merge pprof profiles with unknown location id: 0.') + }) + + it('rejects profiles whose locations reference unknown mapping ids', () => { + expect(() => + mergeProfiles([ + buildProfile({ functionName: 'workerA', sampleValue: 1 }), + buildProfile({ + functionName: 'workerB', + sampleValue: 2, + locationMappingId: 99, + }), + ]) + ).toThrow('Cannot merge pprof profiles with unknown mapping id: 99.') + }) + + it('rejects profiles whose lines reference unknown function ids', () => { + expect(() => + mergeProfiles([ + buildProfile({ functionName: 'workerA', sampleValue: 1 }), + buildProfile({ + functionName: 'workerB', + sampleValue: 2, + lineFunctionId: 99, + }), + ]) + ).toThrow('Cannot merge pprof profiles with unknown function id: 99.') + }) +}) + +describe('mergeStoppedProfileBuffers', () => { + it('merges fulfilled stop buffers and drops empty buffers', () => { + const merged = mergeStoppedProfileBuffers([ + { + status: 'fulfilled', + value: toArrayBuffer(buildProfile({ functionName: 'workerA', sampleValue: 3 }).encode()), + }, + { + status: 'fulfilled', + value: new ArrayBuffer(0), + }, + { + status: 'fulfilled', + value: toArrayBuffer(buildProfile({ functionName: 'workerB', sampleValue: 5 }).encode()), + }, + ]) + + const decoded = Profile.decode(merged) + expect(decoded.sample.map((sample) => sample.value[0])).toEqual([3, 5]) + }) + + it('throws the first rejected stop error', () => { + expect(() => + mergeStoppedProfileBuffers([ + { + status: 'rejected', + reason: new Error('stop failed'), + }, + ]) + ).toThrow('stop failed') + }) +}) diff --git a/src/internal/monitoring/pprof/profile.ts b/src/internal/monitoring/pprof/profile.ts new file mode 100644 index 000000000..27e3999a6 --- /dev/null +++ b/src/internal/monitoring/pprof/profile.ts @@ -0,0 +1,281 @@ +import { + Function as PprofFunction, + Label as PprofLabel, + Line as PprofLine, + Location as PprofLocation, + Mapping as PprofMapping, + Sample as PprofSample, + ValueType as PprofValueType, + Profile, + StringTable, +} from 'pprof-format' + +function numericKey(value: number | bigint) { + return value.toString() +} + +function numericToBigInt(value: number | bigint | undefined) { + if (typeof value === 'bigint') { + return value + } + + if (typeof value === 'number') { + return BigInt(value) + } + + return 0n +} + +function maxNumeric(values: Array) { + let max = 0n + + for (const value of values) { + const numeric = numericToBigInt(value) + if (numeric > max) { + max = numeric + } + } + + return max +} + +function getString(profile: Profile, index: number | bigint | undefined) { + const numericIndex = Number(index ?? 0) + return profile.stringTable.strings[numericIndex] ?? '' +} + +function getValueTypeSignature(profile: Profile, valueType?: PprofValueType) { + if (!valueType) { + return '' + } + + return `${getString(profile, valueType.type)}:${getString(profile, valueType.unit)}` +} + +function getDefaultSampleTypeSignature(profile: Profile) { + return getString(profile, profile.defaultSampleType) +} + +function buildStringIndexMap(profile: Profile, stringTable: StringTable) { + return profile.stringTable.strings.map((entry) => stringTable.dedup(entry)) +} + +function buildProfileMetadataFromFirstProfile( + profile: Profile, + stringIndexMap: number[], + stringTable: StringTable +) { + return { + sampleType: profile.sampleType.map( + (valueType) => + new PprofValueType({ + type: stringIndexMap[Number(valueType.type)] ?? 0, + unit: stringIndexMap[Number(valueType.unit)] ?? 0, + }) + ), + periodType: profile.periodType + ? new PprofValueType({ + type: stringIndexMap[Number(profile.periodType.type)] ?? 0, + unit: stringIndexMap[Number(profile.periodType.unit)] ?? 0, + }) + : undefined, + dropFrames: stringIndexMap[Number(profile.dropFrames)] ?? 0, + keepFrames: stringIndexMap[Number(profile.keepFrames)] ?? 0, + defaultSampleType: stringIndexMap[Number(profile.defaultSampleType)] ?? 0, + stringTable, + } +} + +function getRequiredMappedId( + ids: Map, + value: number | bigint | undefined, + referenceType: 'function' | 'mapping' | 'location', + options: { + allowZero: boolean + } +) { + const numericValue = numericToBigInt(value) + if (options.allowZero && numericValue === 0n) { + return 0 + } + + const mappedId = ids.get(numericKey(numericValue)) + if (mappedId === undefined) { + throw new Error( + `Cannot merge pprof profiles with unknown ${referenceType} id: ${numericValue.toString()}.` + ) + } + + return mappedId +} + +function validateMergeCompatibility(referenceProfile: Profile, profile: Profile) { + if (referenceProfile.sampleType.length !== profile.sampleType.length) { + throw new Error('Cannot merge pprof profiles with different sample type counts.') + } + + for (let index = 0; index < referenceProfile.sampleType.length; index += 1) { + if ( + getValueTypeSignature(referenceProfile, referenceProfile.sampleType[index]) !== + getValueTypeSignature(profile, profile.sampleType[index]) + ) { + throw new Error('Cannot merge pprof profiles with different sample types.') + } + } + + if ( + getValueTypeSignature(referenceProfile, referenceProfile.periodType) !== + getValueTypeSignature(profile, profile.periodType) + ) { + throw new Error('Cannot merge pprof profiles with different period types.') + } + + if (getDefaultSampleTypeSignature(referenceProfile) !== getDefaultSampleTypeSignature(profile)) { + throw new Error('Cannot merge pprof profiles with different default sample types.') + } +} + +export function mergeProfiles(profiles: Profile[]) { + if (profiles.length === 0) { + return Buffer.alloc(0) + } + + if (profiles.length === 1) { + return Buffer.from(profiles[0].encode()) + } + + const stringTable = new StringTable() + const referenceProfile = profiles[0] + const mergedComments = new Set() + const firstStringIndexMap = buildStringIndexMap(referenceProfile, stringTable) + const mergedProfile = new Profile({ + ...buildProfileMetadataFromFirstProfile(referenceProfile, firstStringIndexMap, stringTable), + sample: [], + mapping: [], + location: [], + function: [], + comment: [], + timeNanos: maxNumeric(profiles.map((profile) => profile.timeNanos)), + durationNanos: maxNumeric(profiles.map((profile) => profile.durationNanos)), + period: referenceProfile.period, + }) + + for (let profileIndex = 0; profileIndex < profiles.length; profileIndex += 1) { + const profile = profiles[profileIndex] + if (profileIndex > 0) { + validateMergeCompatibility(referenceProfile, profile) + } + + const stringIndexMap = + profileIndex === 0 ? firstStringIndexMap : buildStringIndexMap(profile, stringTable) + const mappingIds = new Map() + const functionIds = new Map() + const locationIds = new Map() + + for (const mapping of profile.mapping) { + const nextId = mergedProfile.mapping.length + 1 + mappingIds.set(numericKey(mapping.id), nextId) + mergedProfile.mapping.push( + new PprofMapping({ + id: nextId, + memoryStart: mapping.memoryStart, + memoryLimit: mapping.memoryLimit, + fileOffset: mapping.fileOffset, + filename: stringIndexMap[Number(mapping.filename)] ?? 0, + buildId: stringIndexMap[Number(mapping.buildId)] ?? 0, + hasFunctions: mapping.hasFunctions, + hasFilenames: mapping.hasFilenames, + hasLineNumbers: mapping.hasLineNumbers, + hasInlineFrames: mapping.hasInlineFrames, + }) + ) + } + + for (const fn of profile.function) { + const nextId = mergedProfile.function.length + 1 + functionIds.set(numericKey(fn.id), nextId) + mergedProfile.function.push( + new PprofFunction({ + id: nextId, + name: stringIndexMap[Number(fn.name)] ?? 0, + systemName: stringIndexMap[Number(fn.systemName)] ?? 0, + filename: stringIndexMap[Number(fn.filename)] ?? 0, + startLine: fn.startLine, + }) + ) + } + + for (const location of profile.location) { + const nextId = mergedProfile.location.length + 1 + locationIds.set(numericKey(location.id), nextId) + mergedProfile.location.push( + new PprofLocation({ + id: nextId, + mappingId: getRequiredMappedId(mappingIds, location.mappingId, 'mapping', { + allowZero: true, + }), + address: location.address, + isFolded: location.isFolded, + line: location.line.map( + (line) => + new PprofLine({ + functionId: getRequiredMappedId(functionIds, line.functionId, 'function', { + allowZero: true, + }), + line: line.line, + }) + ), + }) + ) + } + + for (const sample of profile.sample) { + mergedProfile.sample.push( + new PprofSample({ + locationId: sample.locationId.map((locationId) => + getRequiredMappedId(locationIds, locationId, 'location', { + allowZero: false, + }) + ), + value: [...sample.value], + label: sample.label.map( + (label) => + new PprofLabel({ + key: stringIndexMap[Number(label.key)] ?? 0, + str: stringIndexMap[Number(label.str)] ?? 0, + num: label.num, + numUnit: stringIndexMap[Number(label.numUnit)] ?? 0, + }) + ), + }) + ) + } + + for (const comment of profile.comment) { + const mappedComment = stringIndexMap[Number(comment)] ?? 0 + if (!mergedComments.has(mappedComment)) { + mergedComments.add(mappedComment) + mergedProfile.comment.push(mappedComment) + } + } + } + + return Buffer.from(mergedProfile.encode()) +} + +export function mergeStoppedProfileBuffers(stopResults: PromiseSettledResult[]) { + const failedStop = stopResults.find((result) => result.status === 'rejected') + if (failedStop?.status === 'rejected') { + throw failedStop.reason + } + + const profiles = stopResults + .filter( + (result): result is PromiseFulfilledResult => result.status === 'fulfilled' + ) + .map((result) => new Uint8Array(result.value)) + .filter((result) => result.byteLength > 0) + .map((result) => Profile.decode(result)) + + return mergeProfiles(profiles) +} diff --git a/src/internal/monitoring/pprof/runtime.test.ts b/src/internal/monitoring/pprof/runtime.test.ts new file mode 100644 index 000000000..012404666 --- /dev/null +++ b/src/internal/monitoring/pprof/runtime.test.ts @@ -0,0 +1,404 @@ +import { vi } from 'vitest' + +const getGlobalMock = vi.hoisted(() => vi.fn()) + +vi.mock('@platformatic/globals', () => ({ + getGlobal: getGlobalMock, +})) + +import { + asProfilingRuntimeApiClient, + buildPprofFilename, + buildPprofResponseHeaders, + buildPprofSessionKey, + buildPprofStartOptions, + createControlStyleError, + getKnownPprofError, + isAbortError, + normalizeNodeModulesSourceMaps, + PPROF_CONTROL_ERROR_CODES, + resolvePprofFilenameTarget, + resolveWattPprofSelection, +} from './runtime' +import type { ProfilingRuntimeApiClient, WattPprofSelection } from './types' + +function createClient(overrides?: { + close?: ProfilingRuntimeApiClient['close'] + getRuntimeApplications?: ProfilingRuntimeApiClient['getRuntimeApplications'] + startApplicationProfiling?: ProfilingRuntimeApiClient['startApplicationProfiling'] + stopApplicationProfiling?: ProfilingRuntimeApiClient['stopApplicationProfiling'] +}) { + return { + close: overrides?.close ?? vi.fn().mockResolvedValue(undefined), + getRuntimeApplications: + overrides?.getRuntimeApplications ?? + vi.fn().mockResolvedValue({ + applications: [{ id: 'storage', workers: 2 }], + }), + startApplicationProfiling: + overrides?.startApplicationProfiling ?? vi.fn().mockResolvedValue(undefined), + stopApplicationProfiling: + overrides?.stopApplicationProfiling ?? vi.fn().mockResolvedValue(new ArrayBuffer(0)), + } satisfies ProfilingRuntimeApiClient +} + +describe('pprof runtime helpers', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('normalizeNodeModulesSourceMaps', () => { + it('trims, de-duplicates, and drops empty values', () => { + expect(normalizeNodeModulesSourceMaps(undefined)).toBeUndefined() + expect(normalizeNodeModulesSourceMaps(' next, ,@next/next-server,next ')).toEqual([ + 'next', + '@next/next-server', + ]) + expect( + normalizeNodeModulesSourceMaps([' next ', '@next/next-server', 'next,@next/next-server']) + ).toEqual(['next', '@next/next-server']) + }) + }) + + describe('buildPprofFilename', () => { + it('sanitizes application ids and includes worker ids when present', () => { + expect( + buildPprofFilename( + { + applicationId: 'storage/api v1', + runtimePid: process.pid, + targetApplicationId: 'storage/api v1:3', + workerId: 3, + }, + 'cpu' + ) + ).toBe('storage-api-v1-worker-3-cpu.pprof') + }) + }) + + describe('resolvePprofFilenameTarget', () => { + it('drops the worker id for whole-app captures', () => { + const selection: WattPprofSelection = { + applicationId: 'storage', + runtimePid: process.pid, + scopeKey: 'all', + targets: [ + { + applicationId: 'storage', + runtimePid: process.pid, + targetApplicationId: 'storage:0', + workerId: 0, + }, + ], + } + + expect(resolvePprofFilenameTarget(selection)).toMatchObject({ + applicationId: 'storage', + workerId: undefined, + }) + }) + }) + + describe('buildPprofResponseHeaders', () => { + it('emits worker-count headers for whole-app captures', () => { + const selection: WattPprofSelection = { + applicationId: 'storage', + runtimePid: process.pid, + scopeKey: 'all', + targets: [ + { + applicationId: 'storage', + runtimePid: process.pid, + targetApplicationId: 'storage:0', + workerId: 0, + }, + { + applicationId: 'storage', + runtimePid: process.pid, + targetApplicationId: 'storage:1', + workerId: 1, + }, + ], + } + + expect(buildPprofResponseHeaders(selection, 'multipart/mixed; boundary=test')).toEqual({ + 'cache-control': 'no-store', + 'content-type': 'multipart/mixed; boundary=test', + 'x-platformatic-application-id': 'storage', + 'x-platformatic-worker-count': '2', + }) + }) + }) + + describe('isAbortError', () => { + it('recognizes DOM and Node abort errors', () => { + expect(isAbortError(new DOMException('Aborted', 'AbortError'))).toBe(true) + expect(isAbortError(Object.assign(new Error('aborted'), { code: 'ABORT_ERR' }))).toBe(true) + expect(isAbortError(new Error('other'))).toBe(false) + }) + }) + + describe('createControlStyleError', () => { + it('attaches the control error code to the error object', () => { + const error = createControlStyleError( + PPROF_CONTROL_ERROR_CODES.profilingAlreadyStarted, + 'busy' + ) + + expect(error).toBeInstanceOf(Error) + expect(error).toMatchObject({ + code: PPROF_CONTROL_ERROR_CODES.profilingAlreadyStarted, + message: 'busy', + }) + }) + }) + + describe('getKnownPprofError', () => { + it('maps known control errors to status codes', () => { + expect( + getKnownPprofError( + Object.assign(new Error('busy'), { + code: PPROF_CONTROL_ERROR_CODES.profilingAlreadyStarted, + }) + ) + ).toEqual({ + code: PPROF_CONTROL_ERROR_CODES.profilingAlreadyStarted, + message: 'busy', + statusCode: 409, + }) + + expect( + getKnownPprofError( + Object.assign(new Error('app missing'), { + code: PPROF_CONTROL_ERROR_CODES.applicationNotFound, + }) + ) + ).toEqual({ + code: PPROF_CONTROL_ERROR_CODES.applicationNotFound, + message: 'app missing', + statusCode: 503, + }) + + expect( + getKnownPprofError( + Object.assign(new Error('control failed'), { + code: PPROF_CONTROL_ERROR_CODES.failedToStart, + cause: Object.assign( + new Error("Cannot find package '@platformatic/wattpm-pprof-capture'"), + { + code: 'ERR_MODULE_NOT_FOUND', + } + ), + }) + ) + ).toEqual({ + code: PPROF_CONTROL_ERROR_CODES.failedToStart, + message: 'control failed', + statusCode: 501, + }) + + expect( + getKnownPprofError( + Object.assign(new Error('missing @platformatic/wattpm-pprof-capture dependency'), { + code: PPROF_CONTROL_ERROR_CODES.failedToStart, + }) + ) + ).toEqual({ + code: PPROF_CONTROL_ERROR_CODES.failedToStart, + message: 'missing @platformatic/wattpm-pprof-capture dependency', + statusCode: 502, + }) + + expect(getKnownPprofError(new Error('other'))).toBeUndefined() + }) + }) + + describe('asProfilingRuntimeApiClient', () => { + it('accepts clients with the profiling control shape', () => { + const client = createClient() + + expect(asProfilingRuntimeApiClient(client)).toBe(client) + }) + + it('rejects clients that are missing profiling control methods', () => { + expect(() => + asProfilingRuntimeApiClient({ + close: async () => {}, + getRuntimeApplications: async () => ({ applications: [] }), + }) + ).toThrow('RuntimeApiClient does not expose the profiling control methods.') + }) + }) + + describe('buildPprofSessionKey', () => { + it('includes application, type, and scope', () => { + expect( + buildPprofSessionKey( + { + applicationId: 'storage', + requestedWorkerId: 7, + runtimePid: process.pid, + scopeKey: 'worker:7', + targets: [], + }, + 'heap' + ) + ).toBe('storage:heap:worker:7') + }) + }) + + describe('buildPprofStartOptions', () => { + it('adds cpu interval and forces sourceMaps when nodeModulesSourceMaps are present', () => { + expect( + buildPprofStartOptions({ + type: 'cpu', + sourceMaps: false, + nodeModulesSourceMaps: ['next'], + }) + ).toEqual({ + type: 'cpu', + intervalMicros: 1000, + nodeModulesSourceMaps: ['next'], + sourceMaps: true, + }) + + expect( + buildPprofStartOptions({ + type: 'heap', + sourceMaps: true, + }) + ).toEqual({ + type: 'heap', + sourceMaps: true, + }) + }) + }) + + describe('resolveWattPprofSelection', () => { + it('returns null outside Watt', async () => { + getGlobalMock.mockReturnValue(undefined) + + await expect(resolveWattPprofSelection(createClient(), undefined)).resolves.toBeNull() + }) + + it('uses the requested worker id directly without enumerating workers', async () => { + const client = createClient() + getGlobalMock.mockReturnValue({ + applicationId: 'storage', + workerId: 2, + }) + + await expect(resolveWattPprofSelection(client, 7)).resolves.toEqual({ + applicationId: 'storage', + requestedWorkerId: 7, + runtimePid: process.pid, + servingWorkerId: 2, + scopeKey: 'worker:7', + targets: [ + { + applicationId: 'storage', + runtimePid: process.pid, + targetApplicationId: 'storage:7', + workerId: 7, + }, + ], + }) + expect(client.getRuntimeApplications).not.toHaveBeenCalled() + }) + + it('enumerates all workers when the application reports more than one worker', async () => { + getGlobalMock.mockReturnValue({ + applicationId: 'storage', + workerId: '1', + }) + + const client = createClient({ + getRuntimeApplications: vi.fn().mockResolvedValue({ + applications: [{ id: 'storage', config: { workers: { minimum: 3 } } }], + }), + }) + + await expect(resolveWattPprofSelection(client, undefined)).resolves.toEqual({ + applicationId: 'storage', + runtimePid: process.pid, + servingWorkerId: 1, + scopeKey: 'all', + targets: [ + { + applicationId: 'storage', + runtimePid: process.pid, + targetApplicationId: 'storage:0', + workerId: 0, + }, + { + applicationId: 'storage', + runtimePid: process.pid, + targetApplicationId: 'storage:1', + workerId: 1, + }, + { + applicationId: 'storage', + runtimePid: process.pid, + targetApplicationId: 'storage:2', + workerId: 2, + }, + ], + }) + }) + + it('falls back to the current worker when only one worker is reported', async () => { + getGlobalMock.mockReturnValue({ + applicationId: 'storage', + workerId: '4', + }) + + const client = createClient({ + getRuntimeApplications: vi.fn().mockResolvedValue({ + applications: [{ id: 'storage', workers: 1 }], + }), + }) + + await expect(resolveWattPprofSelection(client, undefined)).resolves.toEqual({ + applicationId: 'storage', + runtimePid: process.pid, + servingWorkerId: 4, + scopeKey: 'all', + targets: [ + { + applicationId: 'storage', + runtimePid: process.pid, + targetApplicationId: 'storage:4', + workerId: 4, + }, + ], + }) + }) + + it('uses the bare application id when a single-worker app has no worker id in context', async () => { + getGlobalMock.mockReturnValue({ + applicationId: 'storage', + }) + + const client = createClient({ + getRuntimeApplications: vi.fn().mockResolvedValue({ + applications: [{ id: 'storage', workers: 1 }], + }), + }) + + await expect(resolveWattPprofSelection(client, undefined)).resolves.toEqual({ + applicationId: 'storage', + runtimePid: process.pid, + servingWorkerId: undefined, + scopeKey: 'all', + targets: [ + { + applicationId: 'storage', + runtimePid: process.pid, + targetApplicationId: 'storage', + workerId: undefined, + }, + ], + }) + }) + }) +}) diff --git a/src/internal/monitoring/pprof/runtime.ts b/src/internal/monitoring/pprof/runtime.ts new file mode 100644 index 000000000..46b0e66ab --- /dev/null +++ b/src/internal/monitoring/pprof/runtime.ts @@ -0,0 +1,317 @@ +import { getGlobal } from '@platformatic/globals' +import type { + PprofCaptureType, + PprofKnownError, + ProfilingRuntimeApiClient, + RuntimeApplicationWorkersShape, + WattPprofSelection, + WattPprofTarget, +} from './types' + +const CPU_PROFILE_INTERVAL_MICROS = 1000 +const PPROF_CAPTURE_PACKAGE_NAME = '@platformatic/wattpm-pprof-capture' + +export const PPROF_CONTROL_ERROR_CODES = { + applicationNotFound: 'PLT_CTR_APPLICATION_NOT_FOUND', + failedToStart: 'PLT_CTR_FAILED_TO_START_PROFILING', + failedToStop: 'PLT_CTR_FAILED_TO_STOP_PROFILING', + profilingAlreadyStarted: 'PLT_CTR_PROFILING_ALREADY_STARTED', + profilingNotStarted: 'PLT_CTR_PROFILING_NOT_STARTED', + runtimeNotFound: 'PLT_CTR_RUNTIME_NOT_FOUND', +} as const + +function normalizeWorkerId(value: unknown): number | undefined { + if (typeof value === 'number' && Number.isInteger(value) && value >= 0) { + return value + } + + if (typeof value === 'string' && value.trim() !== '') { + const parsed = Number(value) + if (Number.isInteger(parsed) && parsed >= 0) { + return parsed + } + } + + return undefined +} + +function normalizeWorkersCount(value: unknown): number | undefined { + if (typeof value === 'number' && Number.isInteger(value) && value > 0) { + return value + } + + if (typeof value === 'object' && value !== null) { + const workerConfig = value as { + static?: unknown + count?: unknown + minimum?: unknown + } + + return ( + normalizeWorkersCount(workerConfig.static) ?? + normalizeWorkersCount(workerConfig.count) ?? + normalizeWorkersCount(workerConfig.minimum) + ) + } + + return undefined +} + +export function normalizeNodeModulesSourceMaps(value: string | string[] | undefined) { + if (!value) { + return undefined + } + + const modules = [ + ...new Set( + (Array.isArray(value) ? value : [value]) + .flatMap((entry) => entry.split(',')) + .map((entry) => entry.trim()) + .filter(Boolean) + ), + ] + return modules.length > 0 ? modules : undefined +} + +function resolveWattPprofContext() { + const platformatic = getGlobal() + const applicationId = platformatic?.applicationId + + if (!applicationId) { + return null + } + + return { + applicationId, + runtimePid: process.pid, + workerId: normalizeWorkerId(platformatic.workerId), + } +} + +export function buildPprofFilename(target: WattPprofTarget, type: PprofCaptureType) { + const safeApplicationId = target.applicationId.replace(/[^A-Za-z0-9._-]+/g, '-') + const workerSuffix = target.workerId === undefined ? '' : `-worker-${target.workerId}` + + return `${safeApplicationId}${workerSuffix}-${type}.pprof` +} + +export function resolvePprofFilenameTarget(selection: WattPprofSelection) { + return selection.requestedWorkerId === undefined + ? { ...selection.targets[0], workerId: undefined } + : selection.targets[0] +} + +export function buildPprofResponseHeaders(selection: WattPprofSelection, contentType: string) { + return { + 'cache-control': 'no-store', + 'content-type': contentType, + 'x-platformatic-application-id': selection.applicationId, + ...(selection.requestedWorkerId !== undefined + ? { 'x-platformatic-worker-id': `${selection.requestedWorkerId}` } + : { 'x-platformatic-worker-count': `${selection.targets.length}` }), + } +} + +export function isAbortError(error: unknown) { + return ( + error instanceof Error && + (error.name === 'AbortError' || (error as NodeJS.ErrnoException).code === 'ABORT_ERR') + ) +} + +function getControlErrorCode(error: unknown) { + return typeof error === 'object' && + error !== null && + 'code' in error && + typeof error.code === 'string' + ? error.code + : undefined +} + +export function createControlStyleError(code: string, message: string) { + return Object.assign(new Error(message), { code }) +} + +function getErrorMessage(error: unknown) { + return typeof error === 'object' && + error !== null && + 'message' in error && + typeof error.message === 'string' + ? error.message + : undefined +} + +function getErrorCause(error: unknown) { + return typeof error === 'object' && error !== null && 'cause' in error ? error.cause : undefined +} + +function isMissingPprofCaptureDependency(error: unknown) { + const visited = new Set() + let current: unknown = error + + while (current && !visited.has(current)) { + visited.add(current) + + const code = getControlErrorCode(current) + const message = getErrorMessage(current) + if ( + (code === 'MODULE_NOT_FOUND' || code === 'ERR_MODULE_NOT_FOUND') && + message?.includes(PPROF_CAPTURE_PACKAGE_NAME) + ) { + return true + } + + current = getErrorCause(current) + } + + return false +} + +export function getKnownPprofError(error: unknown): PprofKnownError | undefined { + const code = getControlErrorCode(error) + const message = + error instanceof Error + ? error.message + : 'Failed to capture a pprof profile from the Watt runtime.' + + switch (code) { + case PPROF_CONTROL_ERROR_CODES.profilingAlreadyStarted: + case PPROF_CONTROL_ERROR_CODES.profilingNotStarted: + return { code, message, statusCode: 409 } + case PPROF_CONTROL_ERROR_CODES.runtimeNotFound: + case PPROF_CONTROL_ERROR_CODES.applicationNotFound: + return { code, message, statusCode: 503 } + case PPROF_CONTROL_ERROR_CODES.failedToStart: + case PPROF_CONTROL_ERROR_CODES.failedToStop: + return { + code, + message, + statusCode: isMissingPprofCaptureDependency(error) ? 501 : 502, + } + default: + return undefined + } +} + +export function asProfilingRuntimeApiClient(value: unknown): ProfilingRuntimeApiClient { + if ( + typeof value === 'object' && + value !== null && + 'close' in value && + typeof value.close === 'function' && + 'getRuntimeApplications' in value && + typeof value.getRuntimeApplications === 'function' && + 'startApplicationProfiling' in value && + typeof value.startApplicationProfiling === 'function' && + 'stopApplicationProfiling' in value && + typeof value.stopApplicationProfiling === 'function' + ) { + return value as ProfilingRuntimeApiClient + } + + throw new TypeError('RuntimeApiClient does not expose the profiling control methods.') +} + +export function buildPprofSessionKey(selection: WattPprofSelection, type: PprofCaptureType) { + return `${selection.applicationId}:${type}:${selection.scopeKey}` +} + +export function buildPprofStartOptions(options: { + type: PprofCaptureType + nodeModulesSourceMaps?: string[] + sourceMaps?: boolean +}) { + const startOptions: { + type: PprofCaptureType + intervalMicros?: number + nodeModulesSourceMaps?: string[] + sourceMaps?: boolean + } = { + type: options.type, + } + + if (options.type === 'cpu') { + startOptions.intervalMicros = CPU_PROFILE_INTERVAL_MICROS + } + + if (typeof options.sourceMaps === 'boolean') { + startOptions.sourceMaps = options.sourceMaps + } + + if (options.nodeModulesSourceMaps && options.nodeModulesSourceMaps.length > 0) { + startOptions.nodeModulesSourceMaps = options.nodeModulesSourceMaps + startOptions.sourceMaps = true + } + + return startOptions +} + +export async function resolveWattPprofSelection( + client: ProfilingRuntimeApiClient, + requestedWorkerId: number | undefined +) { + const context = resolveWattPprofContext() + + if (!context) { + return null + } + + if (requestedWorkerId !== undefined) { + return { + applicationId: context.applicationId, + requestedWorkerId, + runtimePid: context.runtimePid, + servingWorkerId: context.workerId, + scopeKey: `worker:${requestedWorkerId}`, + targets: [ + { + applicationId: context.applicationId, + runtimePid: context.runtimePid, + targetApplicationId: `${context.applicationId}:${requestedWorkerId}`, + workerId: requestedWorkerId, + }, + ], + } satisfies WattPprofSelection + } + + const runtimeApplications = await client.getRuntimeApplications(context.runtimePid) + const currentApplication = runtimeApplications.applications?.find( + (application) => application.id === context.applicationId + ) as RuntimeApplicationWorkersShape | undefined + const workersCount = + normalizeWorkersCount(currentApplication?.workers) ?? + normalizeWorkersCount(currentApplication?.config?.workers) ?? + 1 + + if (workersCount <= 1) { + const workerId = context.workerId + return { + applicationId: context.applicationId, + runtimePid: context.runtimePid, + servingWorkerId: context.workerId, + scopeKey: 'all', + targets: [ + { + applicationId: context.applicationId, + runtimePid: context.runtimePid, + targetApplicationId: + workerId === undefined ? context.applicationId : `${context.applicationId}:${workerId}`, + workerId, + }, + ], + } satisfies WattPprofSelection + } + + return { + applicationId: context.applicationId, + runtimePid: context.runtimePid, + servingWorkerId: context.workerId, + scopeKey: 'all', + targets: Array.from({ length: workersCount }, (_, workerId) => ({ + applicationId: context.applicationId, + runtimePid: context.runtimePid, + targetApplicationId: `${context.applicationId}:${workerId}`, + workerId, + })), + } satisfies WattPprofSelection +} diff --git a/src/internal/monitoring/pprof/types.ts b/src/internal/monitoring/pprof/types.ts new file mode 100644 index 000000000..3ec6d81dd --- /dev/null +++ b/src/internal/monitoring/pprof/types.ts @@ -0,0 +1,82 @@ +export type PprofCaptureType = 'cpu' | 'heap' +export type PprofRequestTargetType = 'heap' | 'profile' + +export interface WattPprofTarget { + applicationId: string + runtimePid: number + targetApplicationId: string + workerId?: number +} + +export interface WattPprofSelection { + applicationId: string + requestedWorkerId?: number + runtimePid: number + servingWorkerId?: number + scopeKey: string + targets: WattPprofTarget[] +} + +export interface ActivePprofSession extends WattPprofSelection { + key: string + type: PprofCaptureType +} + +export interface PprofCaptureOptions { + signal: AbortSignal + sourceMaps?: boolean + seconds: number + type: PprofCaptureType + nodeModulesSourceMaps?: string[] + workerId?: number +} + +export interface PprofKnownError { + code?: string + message: string + statusCode: number +} + +export interface MultipartPprofWriter { + boundary: string + writeBinaryPart: (headers: Record, body: Buffer) => boolean + writeJsonPart: (payload: unknown) => boolean + close: () => void +} + +export interface ProfilingRuntimeApiClient { + close(): Promise + getRuntimeApplications(pid: number): Promise<{ + applications?: Array<{ + id?: string + workers?: unknown + config?: { + workers?: unknown + } + }> + }> + startApplicationProfiling( + pid: number, + applicationId: string, + options?: { + type: PprofCaptureType + intervalMicros?: number + nodeModulesSourceMaps?: string[] + sourceMaps?: boolean + } + ): Promise + stopApplicationProfiling( + pid: number, + applicationId: string, + options?: { + type: PprofCaptureType + } + ): Promise +} + +export type RuntimeApplicationWorkersShape = { + workers?: unknown + config?: { + workers?: unknown + } +} diff --git a/src/internal/monitoring/system/base-collector.ts b/src/internal/monitoring/system/base-collector.ts new file mode 100644 index 000000000..935bbc2e7 --- /dev/null +++ b/src/internal/monitoring/system/base-collector.ts @@ -0,0 +1,53 @@ +import { Meter } from '@opentelemetry/api' + +export interface MetricCollector { + enable(): void + disable(): void + updateMetricInstruments(meter: Meter): void +} + +export interface CollectorConfig { + prefix?: string + labels?: Record +} + +export abstract class BaseCollector + implements MetricCollector +{ + protected _config: T + protected _enabled = false + + constructor(config: T) { + this._config = config + } + + get namePrefix(): string { + return this._config.prefix ? `${this._config.prefix}.` : '' + } + + get labels(): Record { + return this._config.labels ?? {} + } + + enable(): void { + if (this._enabled) { + return + } + + this._enabled = true + this.internalEnable() + } + + disable(): void { + if (!this._enabled) { + return + } + + this._enabled = false + this.internalDisable() + } + + abstract updateMetricInstruments(meter: Meter): void + protected abstract internalEnable(): void + protected abstract internalDisable(): void +} diff --git a/src/internal/monitoring/system/cpu-collector.ts b/src/internal/monitoring/system/cpu-collector.ts new file mode 100644 index 000000000..98b01afef --- /dev/null +++ b/src/internal/monitoring/system/cpu-collector.ts @@ -0,0 +1,49 @@ +import { Counter, Meter } from '@opentelemetry/api' +import { BaseCollector } from './base-collector' + +export class CpuCollector extends BaseCollector { + private _lastCpuUsage = process.cpuUsage() + private _userCounter: Counter | null = null + private _systemCounter: Counter | null = null + + updateMetricInstruments(meter: Meter): void { + this._userCounter = meter.createCounter(`${this.namePrefix}process.cpu.user`, { + description: 'Total user CPU time spent in seconds', + unit: 's', + }) + + this._systemCounter = meter.createCounter(`${this.namePrefix}process.cpu.system`, { + description: 'Total system CPU time spent in seconds', + unit: 's', + }) + + meter + .createObservableCounter(`${this.namePrefix}process.cpu.total`, { + description: 'Total user and system CPU time spent in seconds', + unit: 's', + }) + .addCallback((observable) => { + if (!this._enabled) return + + const cpuUsage = process.cpuUsage() + const userDelta = (cpuUsage.user - this._lastCpuUsage.user) / 1e6 + const systemDelta = (cpuUsage.system - this._lastCpuUsage.system) / 1e6 + + if (userDelta > 0) this._userCounter?.add(userDelta, this.labels) + if (systemDelta > 0) this._systemCounter?.add(systemDelta, this.labels) + + this._lastCpuUsage = cpuUsage + + const totalSeconds = (cpuUsage.user + cpuUsage.system) / 1e6 + observable.observe(totalSeconds, this.labels) + }) + } + + protected internalEnable(): void { + this._lastCpuUsage = process.cpuUsage() + } + + protected internalDisable(): void { + // no-op + } +} diff --git a/src/internal/monitoring/system/file-descriptor-collector.ts b/src/internal/monitoring/system/file-descriptor-collector.ts new file mode 100644 index 000000000..0fb84b647 --- /dev/null +++ b/src/internal/monitoring/system/file-descriptor-collector.ts @@ -0,0 +1,133 @@ +import { Meter } from '@opentelemetry/api' +import { exec } from 'child_process' +import * as fs from 'fs/promises' +import { promisify } from 'util' +import { BaseCollector } from './base-collector' + +const execAsync = promisify(exec) + +/** + * Collector for file descriptor metrics. + * Works on Linux by reading from /proc/self/fd and /proc/self/limits. + * On other platforms, metrics will not be collected. + */ +export class FileDescriptorCollector extends BaseCollector { + private _maxFds: number | null = null + private _openFds: number | null = null + private _updateInterval: NodeJS.Timeout | null = null + + updateMetricInstruments(meter: Meter): void { + // Open file descriptors gauge + meter + .createObservableGauge(`${this.namePrefix}process.open_fds`, { + description: 'Number of open file descriptors', + }) + .addCallback((observable) => { + if (!this._enabled || this._openFds === null) return + observable.observe(this._openFds, this.labels) + }) + + // Max file descriptors gauge + meter + .createObservableGauge(`${this.namePrefix}process.max_fds`, { + description: 'Maximum number of file descriptors allowed', + }) + .addCallback((observable) => { + if (!this._enabled || this._maxFds === null) return + observable.observe(this._maxFds, this.labels) + }) + } + + /** + * Get the number of open file descriptors + * On macOS: counts entries in /dev/fd + * On Linux: counts entries in /proc/self/fd + */ + private async getOpenFileDescriptors(): Promise { + try { + // Both macOS (/dev/fd) and Linux (/proc/self/fd) support directory-based FD counting + const fdPath = process.platform === 'darwin' ? '/dev/fd' : '/proc/self/fd' + + if (process.platform === 'darwin' || process.platform === 'linux') { + const fds = await fs.readdir(fdPath) + // Subtract 1 because readdir itself opens an FD + return Math.max(0, fds.length - 1) + } + + return null + } catch { + return null + } + } + + /** + * Get the maximum number of file descriptors + * On macOS: uses ulimit -n command + * On Linux: parses /proc/self/limits + */ + private async getMaxFileDescriptors(): Promise { + if (this._maxFds !== null) { + return this._maxFds + } + + try { + // macOS: use ulimit -n command + if (process.platform === 'darwin') { + const { stdout } = await execAsync('ulimit -n') + const limit = parseInt(stdout.trim(), 10) + if (!isNaN(limit)) { + this._maxFds = limit + return limit + } + return null + } + + // Linux: read from /proc/self/limits + if (process.platform === 'linux') { + const limits = await fs.readFile('/proc/self/limits', 'utf8') + const lines = limits.split('\n') + + for (const line of lines) { + if (line.startsWith('Max open files')) { + const parts = line.split(/\s+/) + const softLimit = parseInt(parts[3], 10) + if (!isNaN(softLimit)) { + this._maxFds = softLimit + return softLimit + } + } + } + } + + return null + } catch { + return null + } + } + + protected internalEnable(): void { + this._maxFds = null + this._openFds = null + + // Update metrics periodically + this.updateMetrics().catch(() => { + // Ignore errors + }) + this._updateInterval = setInterval(() => this.updateMetrics().catch(() => {}), 5000) + this._updateInterval.unref() + } + + protected internalDisable(): void { + if (this._updateInterval) { + clearInterval(this._updateInterval) + this._updateInterval = null + } + this._maxFds = null + this._openFds = null + } + + private async updateMetrics(): Promise { + this._maxFds = await this.getMaxFileDescriptors() + this._openFds = await this.getOpenFileDescriptors() + } +} diff --git a/src/internal/monitoring/system/handles-collector.ts b/src/internal/monitoring/system/handles-collector.ts new file mode 100644 index 000000000..f4c74ac76 --- /dev/null +++ b/src/internal/monitoring/system/handles-collector.ts @@ -0,0 +1,36 @@ +import { Meter } from '@opentelemetry/api' +import { BaseCollector } from './base-collector' + +export class HandlesCollector extends BaseCollector { + updateMetricInstruments(meter: Meter): void { + meter + .createObservableGauge(`${this.namePrefix}nodejs.active_handles.total`, { + description: 'Number of active libuv handles', + }) + .addCallback((observable) => { + if (!this._enabled) return + // @ts-expect-error - _getActiveHandles is not in types but exists + const handles = process._getActiveHandles?.() || [] + observable.observe(handles.length, this.labels) + }) + + meter + .createObservableGauge(`${this.namePrefix}nodejs.active_requests.total`, { + description: 'Number of active libuv requests', + }) + .addCallback((observable) => { + if (!this._enabled) return + // @ts-expect-error - _getActiveRequests is not in types but exists + const requests = process._getActiveRequests?.() || [] + observable.observe(requests.length, this.labels) + }) + } + + protected internalEnable(): void { + // no-op + } + + protected internalDisable(): void { + // no-op + } +} diff --git a/src/internal/monitoring/system/index.ts b/src/internal/monitoring/system/index.ts new file mode 100644 index 000000000..1f7e39ad6 --- /dev/null +++ b/src/internal/monitoring/system/index.ts @@ -0,0 +1,6 @@ +export { BaseCollector, CollectorConfig, MetricCollector } from './base-collector' +export { CpuCollector } from './cpu-collector' +export { HandlesCollector } from './handles-collector' +export { StorageNodeInstrumentation, StorageNodeInstrumentationConfig } from './instrumentation' +export { ExternalMemoryCollector } from './memory-collector' +export { ProcessStartCollector } from './process-start-collector' diff --git a/src/internal/monitoring/system/instrumentation.ts b/src/internal/monitoring/system/instrumentation.ts new file mode 100644 index 000000000..fb7fddf1c --- /dev/null +++ b/src/internal/monitoring/system/instrumentation.ts @@ -0,0 +1,82 @@ +import { InstrumentationBase, InstrumentationConfig } from '@opentelemetry/instrumentation' +import { CollectorConfig, MetricCollector } from './base-collector' +import { CpuCollector } from './cpu-collector' +import { FileDescriptorCollector } from './file-descriptor-collector' +import { HandlesCollector } from './handles-collector' +import { ExternalMemoryCollector } from './memory-collector' +import { ProcessStartCollector } from './process-start-collector' + +export interface StorageNodeInstrumentationConfig extends InstrumentationConfig, CollectorConfig {} + +const DEFAULT_CONFIG: StorageNodeInstrumentationConfig = { + prefix: '', + labels: {}, +} + +/** + * Custom Node.js runtime instrumentation that provides metrics + * not covered by @opentelemetry/instrumentation-runtime-node: + * - Event loop lag (setImmediate measurement) + * - CPU usage (user, system, total) + * - Active handles and requests + * - Process start time + * - External memory, ArrayBuffers, RSS + * - File descriptors (open and max, Linux only) + * + * Use alongside RuntimeNodeInstrumentation for complete coverage. + */ +export class StorageNodeInstrumentation extends InstrumentationBase { + private _collectors: MetricCollector[] = [] + + constructor(config: StorageNodeInstrumentationConfig = {}) { + const mergedConfig = { ...DEFAULT_CONFIG, ...config } + super('@storage/instrumentation-node', '1.0.0', mergedConfig) + + const collectorConfig: CollectorConfig = { + prefix: mergedConfig.prefix, + labels: mergedConfig.labels, + } + + this._collectors = [ + new CpuCollector(collectorConfig), + new HandlesCollector(collectorConfig), + new ProcessStartCollector(collectorConfig), + new ExternalMemoryCollector(collectorConfig), + new FileDescriptorCollector(collectorConfig), + ] + + // Enable collectors if instrumentation is enabled (matches RuntimeNodeInstrumentation pattern) + if (this._config.enabled) { + for (const collector of this._collectors) { + collector.enable() + } + } + } + + init() { + // Not instrumenting or patching a Node.js module + } + + // Called when a new MeterProvider is set + override _updateMetricInstruments() { + if (!this._collectors) return + for (const collector of this._collectors) { + collector.updateMetricInstruments(this.meter) + } + } + + override enable() { + super.enable() + if (!this._collectors) return + for (const collector of this._collectors) { + collector.enable() + } + } + + override disable() { + super.disable() + for (const collector of this._collectors) { + collector.disable() + } + } +} diff --git a/src/internal/monitoring/system/memory-collector.ts b/src/internal/monitoring/system/memory-collector.ts new file mode 100644 index 000000000..f68b15ff2 --- /dev/null +++ b/src/internal/monitoring/system/memory-collector.ts @@ -0,0 +1,63 @@ +import { Meter } from '@opentelemetry/api' +import { BaseCollector } from './base-collector' + +export class ExternalMemoryCollector extends BaseCollector { + updateMetricInstruments(meter: Meter): void { + meter + .createObservableGauge(`${this.namePrefix}nodejs.memory.external`, { + description: 'Node.js external memory size in bytes', + unit: 'By', + }) + .addCallback((observable) => { + if (!this._enabled) return + try { + const mem = process.memoryUsage() + if (mem.external !== undefined) { + observable.observe(mem.external, this.labels) + } + } catch { + // ignore errors + } + }) + + meter + .createObservableGauge(`${this.namePrefix}nodejs.memory.array_buffers`, { + description: 'Node.js ArrayBuffers memory size in bytes', + unit: 'By', + }) + .addCallback((observable) => { + if (!this._enabled) return + try { + const mem = process.memoryUsage() + if (mem.arrayBuffers !== undefined) { + observable.observe(mem.arrayBuffers, this.labels) + } + } catch { + // ignore errors + } + }) + + meter + .createObservableGauge(`${this.namePrefix}nodejs.memory.rss`, { + description: 'Resident Set Size - total memory allocated for the process', + unit: 'By', + }) + .addCallback((observable) => { + if (!this._enabled) return + try { + const mem = process.memoryUsage() + observable.observe(mem.rss, this.labels) + } catch { + // ignore errors + } + }) + } + + protected internalEnable(): void { + // no-op + } + + protected internalDisable(): void { + // no-op + } +} diff --git a/src/internal/monitoring/system/process-start-collector.ts b/src/internal/monitoring/system/process-start-collector.ts new file mode 100644 index 000000000..ddcc1e2a1 --- /dev/null +++ b/src/internal/monitoring/system/process-start-collector.ts @@ -0,0 +1,26 @@ +import { Meter } from '@opentelemetry/api' +import { BaseCollector } from './base-collector' + +const START_TIME_SECONDS = Math.round(Date.now() / 1000 - process.uptime()) + +export class ProcessStartCollector extends BaseCollector { + updateMetricInstruments(meter: Meter): void { + meter + .createObservableGauge(`${this.namePrefix}process.start_time`, { + description: 'Start time of the process since unix epoch in seconds', + unit: 's', + }) + .addCallback((observable) => { + if (!this._enabled) return + observable.observe(START_TIME_SECONDS, this.labels) + }) + } + + protected internalEnable(): void { + // no-op + } + + protected internalDisable(): void { + // no-op + } +} diff --git a/src/internal/pubsub/index.ts b/src/internal/pubsub/index.ts index 0381d0d6f..6aeb06d82 100644 --- a/src/internal/pubsub/index.ts +++ b/src/internal/pubsub/index.ts @@ -1,2 +1,2 @@ -export * from './postgres' export * from './adapter' +export * from './postgres' diff --git a/src/internal/pubsub/postgres.ts b/src/internal/pubsub/postgres.ts index dc0263e9b..7221a0bc2 100644 --- a/src/internal/pubsub/postgres.ts +++ b/src/internal/pubsub/postgres.ts @@ -1,7 +1,7 @@ import EventEmitter from 'node:events' -import createSubscriber, { Subscriber } from 'pg-listen' import { ERRORS } from '@internal/errors' import { logger, logSchema } from '@internal/monitoring' +import createSubscriber, { Subscriber } from 'pg-listen' import { PubSubAdapter } from './adapter' export class PostgresPubSub extends EventEmitter implements PubSubAdapter { @@ -53,6 +53,7 @@ export class PostgresPubSub extends EventEmitter implements PubSubAdapter { } async close(): Promise { + this.isConnected = false this.subscriber.notifications.eventNames().forEach((event) => { this.subscriber.notifications.removeAllListeners(event) }) @@ -80,7 +81,7 @@ export class PostgresPubSub extends EventEmitter implements PubSubAdapter { const isListening = this.subscriber.notifications.listenerCount(channel) > 0 - if (!isListening) { + if (!isListening && this.isConnected) { await this.subscriber.unlisten(channel) } } diff --git a/src/internal/queue/database.ts b/src/internal/queue/database.ts index 2eb2a6634..f4b343cb3 100644 --- a/src/internal/queue/database.ts +++ b/src/internal/queue/database.ts @@ -1,7 +1,9 @@ -import { Db } from 'pg-boss' -import EventEmitter from 'events' -import pg from 'pg' +import EventEmitter from 'node:events' import { ERRORS } from '@internal/errors' +import { Knex } from 'knex' +import pg from 'pg' +import { Db } from 'pg-boss' +import { getConfig } from '../../config' export class QueueDB extends EventEmitter implements Db { opened = false @@ -15,7 +17,7 @@ export class QueueDB extends EventEmitter implements Db { constructor(config: pg.PoolConfig) { super() - config.application_name = config.application_name || 'pgboss' + config.application_name = config.application_name || getConfig().pgQueueApplicationName this.config = config } @@ -32,11 +34,78 @@ export class QueueDB extends EventEmitter implements Db { await this.pool?.end() } + protected async useTransaction(fn: (client: pg.PoolClient) => Promise): Promise { + if (!this.opened || !this.pool) { + throw ERRORS.InternalError(undefined, `QueueDB not opened ${this.opened}`) + } + + const client = await this.pool.connect() + + // Create a promise that rejects if the client emits an error + // (e.g. connection lost, statement_timeout at the backend level) + let clientError: Error | undefined + const onError = (e: Error) => { + clientError = e + } + client.on('error', onError) + + try { + await client.query('BEGIN') + + if (this.config.statement_timeout && this.config.statement_timeout > 0) { + await client.query(`SET LOCAL statement_timeout = ${this.config.statement_timeout}`) + } + + const result = await fn(client) + + if (clientError) { + throw clientError + } + + await client.query('COMMIT') + return result + } catch (err) { + const rollbackErr = await client.query('ROLLBACK').catch((e) => e as Error) + + const errors = [err as Error, clientError, rollbackErr].filter( + (e): e is Error => e instanceof Error + ) + + if (errors.length === 1) throw errors[0] + throw new AggregateError(errors, 'Queue transaction failed') + } finally { + client.off('error', onError) + client.release(clientError) + } + } + async executeSql(text: string, values: any[]) { if (this.opened && this.pool) { - return await this.pool.query(text, values) + return this.useTransaction((client) => client.query(text, values)) } throw ERRORS.InternalError(undefined, `QueueDB not opened ${this.opened} ${text}`) } } + +export class KnexQueueDB extends EventEmitter implements Db { + events = { + error: 'error', + } + + constructor(protected readonly knex: Knex) { + super() + } + + async executeSql(text: string, values: any[]): Promise<{ rows: any[] }> { + const knexQuery = text.replaceAll('$', ':') + const params: Record = {} + + values.forEach((value, index) => { + const key = (index + 1).toString() + params[key] = value === undefined ? null : value + }) + const result = await this.knex.raw(knexQuery, params) + return { rows: result.rows } + } +} diff --git a/src/internal/queue/event.test.ts b/src/internal/queue/event.test.ts new file mode 100644 index 000000000..ba2a1c8eb --- /dev/null +++ b/src/internal/queue/event.test.ts @@ -0,0 +1,141 @@ +import { vi } from 'vitest' + +type EventModule = typeof import('./event') +type QueueModule = typeof import('./queue') + +type TestPayload = { + name: string + tenant: { + ref: string + host: string + } + reqId?: string + scheduleAt?: Date +} + +async function loadQueueModules(opts?: { pgQueueEnable?: boolean }) { + vi.resetModules() + + const configModule = await import('../../config') + configModule.getConfig({ reload: true }) + configModule.mergeConfig({ + pgQueueEnable: opts?.pgQueueEnable ?? false, + }) + + const eventModule = (await import('./event')) as EventModule + const queueModule = (await import('./queue')) as QueueModule + + return { eventModule, queueModule } +} + +function defineTestEvent(EventBase: EventModule['Event']) { + return class TestEvent extends EventBase { + static readonly version = 'v-test' + protected static queueName = 'test-event' + } +} + +function createPayload(overrides: Partial = {}): TestPayload { + return { + name: 'test-object', + tenant: { + ref: 'test-tenant', + host: 'localhost', + }, + reqId: 'req-123', + ...overrides, + } +} + +describe('Event payload versioning', () => { + afterEach(() => { + vi.restoreAllMocks() + vi.resetModules() + }) + + it('does not mutate payloads passed to static send', async () => { + const { eventModule } = await loadQueueModules() + const TestEvent = defineTestEvent(eventModule.Event) + const payload = createPayload() + + vi.spyOn(TestEvent.prototype, 'send').mockImplementation(function ( + this: InstanceType + ) { + expect(this.payload.$version).toBe('v-test') + return Promise.resolve('queued') + }) + + await TestEvent.send(payload) + + expect(payload).toEqual(createPayload()) + expect(payload).not.toHaveProperty('$version') + }) + + it('does not mutate payloads passed to static invoke', async () => { + const { eventModule } = await loadQueueModules() + const TestEvent = defineTestEvent(eventModule.Event) + const payload = createPayload() + + vi.spyOn(TestEvent.prototype, 'invoke').mockImplementation(function ( + this: InstanceType + ) { + expect(this.payload.$version).toBe('v-test') + return Promise.resolve(null) + }) + + await TestEvent.invoke(payload) + + expect(payload).toEqual(createPayload()) + expect(payload).not.toHaveProperty('$version') + }) + + it('does not mutate payloads passed to static invokeOrSend', async () => { + const { eventModule } = await loadQueueModules() + const TestEvent = defineTestEvent(eventModule.Event) + const payload = createPayload() + + vi.spyOn(TestEvent.prototype, 'invokeOrSend').mockImplementation(function ( + this: InstanceType + ) { + expect(this.payload.$version).toBe('v-test') + return Promise.resolve(null) + }) + + await TestEvent.invokeOrSend(payload) + + expect(payload).toEqual(createPayload()) + expect(payload).not.toHaveProperty('$version') + }) + + it('does not mutate message payloads when batching', async () => { + const { eventModule, queueModule } = await loadQueueModules({ pgQueueEnable: true }) + const TestEvent = defineTestEvent(eventModule.Event) + const payload = createPayload({ scheduleAt: new Date('2026-04-07T10:00:00.000Z') }) + const insert = vi.fn().mockResolvedValue('job-id') + + vi.spyOn(queueModule.Queue, 'getInstance').mockReturnValue({ insert } as unknown as ReturnType< + typeof queueModule.Queue.getInstance + >) + + const message = new TestEvent(payload) + + await TestEvent.batchSend([message]) + + expect(payload).toEqual(createPayload({ scheduleAt: new Date('2026-04-07T10:00:00.000Z') })) + expect(payload).not.toHaveProperty('$version') + expect(insert).toHaveBeenCalledWith([ + expect.objectContaining({ + name: 'test-event', + deadLetter: 'test-event-dead-letter', + data: expect.objectContaining({ + $version: 'v-test', + name: 'test-object', + tenant: expect.objectContaining({ + ref: 'test-tenant', + }), + }), + startAfter: new Date('2026-04-07T10:00:00.000Z'), + }), + ]) + }) +}) diff --git a/src/internal/queue/event.ts b/src/internal/queue/event.ts index 39f2b79b6..671fd0397 100644 --- a/src/internal/queue/event.ts +++ b/src/internal/queue/event.ts @@ -1,10 +1,12 @@ -import { Queue } from './queue' -import PgBoss, { Job, SendOptions, WorkOptions, Queue as PgBossQueue } from 'pg-boss' -import { getConfig } from '../../config' -import { QueueJobScheduled, QueueJobSchedulingTime } from '@internal/monitoring/metrics' -import { logger, logSchema } from '@internal/monitoring' import { getTenantConfig } from '@internal/database' import { ERRORS } from '@internal/errors' +import { logger, logSchema } from '@internal/monitoring' +import { queueJobScheduled, queueJobSchedulingTime } from '@internal/monitoring/metrics' +import { KnexQueueDB } from '@internal/queue/database' +import { Knex } from 'knex' +import PgBoss, { Job, Queue as PgBossQueue, SendOptions, WorkOptions } from 'pg-boss' +import { getConfig } from '../../config' +import { PG_BOSS_SCHEMA, Queue } from './queue' export interface BasePayload { $version?: string @@ -19,6 +21,16 @@ export interface BasePayload { const { pgQueueEnable, region, isMultitenant } = getConfig() +function withPayloadVersion( + payload: TPayload, + version: string +): TPayload { + return { + ...payload, + $version: payload.$version ?? version, + } +} + export type StaticThis> = BaseEventConstructor interface BaseEventConstructor> { @@ -86,54 +98,65 @@ export class Event> { if (this.allowSync) { return Promise.all(messages.map((message) => message.send())) } else { - logger.warn('[Queue] skipped sending batch messages', { - type: 'queue', - eventType: this.eventName(), - }) + logger.warn( + { + type: 'queue', + eventType: this.eventName(), + }, + '[Queue] skipped sending batch messages' + ) return } } return Queue.getInstance().insert( messages.map((message) => { - const sendOptions = (this.getSendOptions(message.payload) as PgBoss.JobInsert) || {} - if (!message.payload.$version) { - ;(message.payload as (typeof message)['payload']).$version = this.version - } - - if (message.payload.scheduleAt) { - sendOptions.startAfter = new Date(message.payload.scheduleAt) + const payloadWithVersion = withPayloadVersion( + message.payload as (typeof message)['payload'], + this.version + ) + const sendOptions = (this.getSendOptions(payloadWithVersion) as PgBoss.JobInsert) || {} + + if (payloadWithVersion.scheduleAt) { + sendOptions.startAfter = new Date(payloadWithVersion.scheduleAt) } return { ...sendOptions, name: this.getQueueName(), - data: message.payload, + data: payloadWithVersion, deadLetter: this.deadLetterQueueName(), } }) ) } - static send>(this: StaticThis, payload: Omit) { - if (!payload.$version) { - ;(payload as T['payload']).$version = this.version - } - const that = new this(payload) - return that.send() + static send>( + this: StaticThis, + payload: Omit, + opts?: SendOptions & { tnx?: Knex } + ) { + const that = new this(withPayloadVersion(payload as T['payload'], this.version)) + return that.send(opts) } static invoke>( this: StaticThis, payload: Omit ) { - if (!payload.$version) { - ;(payload as T['payload']).$version = this.version - } - const that = new this(payload) + const that = new this(withPayloadVersion(payload as T['payload'], this.version)) return that.invoke() } + static invokeOrSend>( + this: StaticThis, + payload: Omit, + options?: SendOptions & { sendWhenError?: (error: unknown) => boolean } + ) { + const that = new this(withPayloadVersion(payload as T['payload'], this.version)) + return that.invokeOrSend(options) + } + static handle( job: Job['payload']> | Job['payload']>[], opts?: { signal?: AbortSignal } @@ -165,10 +188,10 @@ export class Event> { } await Queue.getDb().executeSql( - `DELETE FROM pgboss_v10.job + `DELETE FROM ${PG_BOSS_SCHEMA}.job WHERE id = $1 AND EXISTS( - SELECT 1 FROM pgboss_v10.job + SELECT 1 FROM ${PG_BOSS_SCHEMA}.job WHERE id != $2 AND state < 'active' AND name = $3 @@ -179,77 +202,116 @@ export class Event> { ) } + async invokeOrSend( + sendOptions?: SendOptions & { sendWhenError?: (error: unknown) => boolean } + ): Promise { + const eventClass = this.constructor as typeof Event + + if (!eventClass.allowSync) { + throw ERRORS.InternalError(undefined, 'Cannot send this event synchronously') + } + + try { + await this.invoke() + } catch (e) { + if (sendOptions?.sendWhenError && !sendOptions.sendWhenError(e)) { + throw e + } + logSchema.error(logger, '[Queue] Error invoking event synchronously, sending to queue', { + type: 'queue', + project: this.payload.tenant?.ref, + error: e, + metadata: JSON.stringify(this.payload), + }) + + return this.send(sendOptions) + } + } + async invoke(): Promise { - const constructor = this.constructor as typeof Event + const eventClass = this.constructor as typeof Event - if (!constructor.allowSync) { + if (!eventClass.allowSync) { throw ERRORS.InternalError(undefined, 'Cannot send this event synchronously') } - await constructor.handle({ + await eventClass.handle({ id: '__sync', expireInSeconds: 0, - name: constructor.getQueueName(), + name: eventClass.getQueueName(), data: { region, ...this.payload, - $version: constructor.version, + $version: eventClass.version, }, }) } - async send(): Promise { - const constructor = this.constructor as typeof Event + async send(customSendOptions?: SendOptions & { tnx?: Knex }): Promise { + const eventClass = this.constructor as typeof Event - const shouldSend = await constructor.shouldSend(this.payload) + const shouldSend = await eventClass.shouldSend(this.payload) if (!shouldSend) { return } if (!pgQueueEnable) { - if (constructor.allowSync) { - return constructor.handle({ + if (eventClass.allowSync) { + return eventClass.handle({ id: '__sync', expireInSeconds: 0, - name: constructor.getQueueName(), + name: eventClass.getQueueName(), data: { region, ...this.payload, - $version: constructor.version, + $version: eventClass.version, }, }) } else { - logger.warn('[Queue] skipped sending message', { - type: 'queue', - eventType: constructor.eventName(), - }) + logger.warn( + { + type: 'queue', + eventType: eventClass.eventName(), + }, + '[Queue] skipped sending message' + ) return } } - const timer = QueueJobSchedulingTime.startTimer() - const sendOptions = constructor.getSendOptions(this.payload) || {} + const startTime = process.hrtime.bigint() + const sendOptions = eventClass.getSendOptions(this.payload) || {} if (this.payload.scheduleAt) { sendOptions.startAfter = new Date(this.payload.scheduleAt) } - sendOptions!.deadLetter = constructor.deadLetterQueueName() + sendOptions!.deadLetter = eventClass.deadLetterQueueName() try { - const res = await Queue.getInstance().send({ - name: constructor.getQueueName(), + const queue = customSendOptions?.tnx + ? Queue.createPgBoss({ + enableWorkers: false, + db: new KnexQueueDB(customSendOptions.tnx), + }) + : Queue.getInstance() + + const res = await queue.send({ + name: eventClass.getQueueName(), data: { region, ...this.payload, - $version: constructor.version, + $version: eventClass.version, + }, + options: { + ...sendOptions, + ...customSendOptions, }, - options: sendOptions, }) - QueueJobScheduled.inc({ - name: constructor.getQueueName(), + queueJobScheduled.add(1, { + name: eventClass.getQueueName(), }) return res @@ -266,19 +328,25 @@ export class Event> { metadata: JSON.stringify(this.payload), } ) - return constructor.handle({ + + if (!eventClass.allowSync) { + throw e + } + + return eventClass.handle({ id: '__sync', expireInSeconds: 0, - name: constructor.getQueueName(), + name: eventClass.getQueueName(), data: { region, ...this.payload, - $version: constructor.version, + $version: eventClass.version, }, }) } finally { - timer({ - name: constructor.getQueueName(), + const duration = Number(process.hrtime.bigint() - startTime) / 1e9 + queueJobSchedulingTime.record(duration, { + name: eventClass.getQueueName(), }) } } diff --git a/src/internal/queue/index.ts b/src/internal/queue/index.ts index 0210f46bb..209afebc0 100644 --- a/src/internal/queue/index.ts +++ b/src/internal/queue/index.ts @@ -1,2 +1,2 @@ -export * from './queue' export * from './event' +export * from './queue' diff --git a/src/internal/queue/queue.ts b/src/internal/queue/queue.ts index d0316e3b8..102fc7009 100644 --- a/src/internal/queue/queue.ts +++ b/src/internal/queue/queue.ts @@ -1,22 +1,73 @@ -import PgBoss, { Job, JobWithMetadata } from 'pg-boss' import { ERRORS } from '@internal/errors' import { QueueDB } from '@internal/queue/database' +import { Semaphore } from '@shopify/semaphore' +import PgBoss, { Db, Job, JobWithMetadata } from 'pg-boss' import { getConfig } from '../../config' import { logger, logSchema } from '../monitoring' -import { QueueJobRetryFailed, QueueJobCompleted, QueueJobError } from '../monitoring/metrics' +import { queueJobCompleted, queueJobError, queueJobRetryFailed } from '../monitoring/metrics' import { Event } from './event' -import { Semaphore } from '@shopify/semaphore' -//eslint-disable-next-line @typescript-eslint/no-explicit-any -type SubclassOfBaseClass = (new (payload: any) => Event) & { +type SubclassOfBaseClass = (new ( + payload: any +) => Event) & { [K in keyof typeof Event]: (typeof Event)[K] } +export const PG_BOSS_SCHEMA = 'pgboss_v10' + export abstract class Queue { protected static events: SubclassOfBaseClass[] = [] private static pgBoss?: PgBoss private static pgBossDb?: PgBoss.Db + static createPgBoss(opts: { db: Db; enableWorkers: boolean }) { + const { + isMultitenant, + databaseURL, + multitenantDatabasePoolUrl, + multitenantDatabaseUrl, + pgQueueConnectionURL, + pgQueueArchiveCompletedAfterSeconds, + pgQueueDeleteAfterDays, + pgQueueDeleteAfterHours, + pgQueueRetentionDays, + } = getConfig() + + let url = pgQueueConnectionURL ?? databaseURL + let migrate = true + + if (isMultitenant && !pgQueueConnectionURL) { + if (!multitenantDatabaseUrl) { + throw new Error( + 'running storage in multi-tenant but DB_MULTITENANT_DATABASE_URL is not set' + ) + } + url = multitenantDatabasePoolUrl || multitenantDatabaseUrl + + if (multitenantDatabasePoolUrl) { + migrate = false + } + } + + return new PgBoss({ + connectionString: url, + migrate, + db: opts.db, + schema: PG_BOSS_SCHEMA, + ...(pgQueueDeleteAfterHours + ? { deleteAfterHours: pgQueueDeleteAfterHours } + : { deleteAfterDays: pgQueueDeleteAfterDays }), + archiveCompletedAfterSeconds: pgQueueArchiveCompletedAfterSeconds, + retentionDays: pgQueueRetentionDays, + retryBackoff: true, + retryLimit: 20, + expireInHours: 23, + maintenanceIntervalSeconds: 60 * 5, + schedule: opts.enableWorkers, + supervise: opts.enableWorkers, + }) + } + static async start(opts: { signal?: AbortSignal onMessage?: (job: Job) => void @@ -34,26 +85,23 @@ export abstract class Queue { isMultitenant, databaseURL, multitenantDatabaseUrl, + multitenantDatabasePoolUrl, pgQueueConnectionURL, - pgQueueArchiveCompletedAfterSeconds, - pgQueueDeleteAfterDays, - pgQueueDeleteAfterHours, - pgQueueRetentionDays, pgQueueEnableWorkers, pgQueueReadWriteTimeout, pgQueueConcurrentTasksPerQueue, pgQueueMaxConnections, } = getConfig() - let url = pgQueueConnectionURL ?? databaseURL + let url = pgQueueConnectionURL || databaseURL if (isMultitenant && !pgQueueConnectionURL) { - if (!multitenantDatabaseUrl) { + if (!multitenantDatabaseUrl && !multitenantDatabasePoolUrl) { throw new Error( 'running storage in multi-tenant but DB_MULTITENANT_DATABASE_URL is not set' ) } - url = multitenantDatabaseUrl + url = (multitenantDatabasePoolUrl || multitenantDatabaseUrl) as string } Queue.pgBossDb = new QueueDB({ @@ -63,23 +111,9 @@ export abstract class Queue { statement_timeout: pgQueueReadWriteTimeout > 0 ? pgQueueReadWriteTimeout : undefined, }) - Queue.pgBoss = new PgBoss({ - connectionString: url, - migrate: true, + Queue.pgBoss = this.createPgBoss({ db: Queue.pgBossDb, - schema: 'pgboss_v10', - application_name: 'storage-pgboss', - ...(pgQueueDeleteAfterHours - ? { deleteAfterHours: pgQueueDeleteAfterHours } - : { deleteAfterDays: pgQueueDeleteAfterDays }), - archiveCompletedAfterSeconds: pgQueueArchiveCompletedAfterSeconds, - retentionDays: pgQueueRetentionDays, - retryBackoff: true, - retryLimit: 20, - expireInHours: 23, - maintenanceIntervalSeconds: 60 * 5, - schedule: pgQueueEnableWorkers !== false, - supervise: pgQueueEnableWorkers !== false, + enableWorkers: pgQueueEnableWorkers !== false, }) Queue.pgBoss.on('error', (error) => { @@ -168,12 +202,7 @@ export abstract class Queue { wait: true, }) - await new Promise((resolve) => { - boss.once('stopped', async () => { - await this.callClose() - resolve(null) - }) - }) + await this.callClose() Queue.pgBoss = undefined } @@ -264,8 +293,8 @@ export abstract class Queue { type: 'queue', metadata: JSON.stringify({ queueName: event.getQueueName(), - batchSize: batchSize, - pollingInterval: pollingInterval, + batchSize, + pollingInterval, }), }) @@ -321,11 +350,11 @@ export abstract class Queue { await event.handle(job, { signal: queueOpts.signal }) await this.pgBoss?.complete(event.getQueueName(), job.id) - QueueJobCompleted.inc({ + queueJobCompleted.add(1, { name: event.getQueueName(), }) } catch (e) { - QueueJobRetryFailed.inc({ + queueJobRetryFailed.add(1, { name: event.getQueueName(), }) @@ -341,7 +370,7 @@ export abstract class Queue { return } if (dbJob.retryCount >= dbJob.retryLimit) { - QueueJobError.inc({ + queueJobError.add(1, { name: event.getQueueName(), }) } diff --git a/src/internal/sharding/architecture.md b/src/internal/sharding/architecture.md new file mode 100644 index 000000000..10d804aa8 --- /dev/null +++ b/src/internal/sharding/architecture.md @@ -0,0 +1,471 @@ +# Sharding Implementation + +This document explains how the sharding system works, how it allocates slots, manages reservations, and handles slot reuse. +This document was generated with Calude Code. A human did architecture & implementation. + +## Overview + +The sharding system provides a way to distribute resources (like vector indexes or Iceberg tables) across multiple physical shards while maintaining a logical namespace for tenants. It ensures that resources are evenly distributed and that capacity is managed efficiently. + +## Core Concepts + +### 1. Shards + +A **shard** is a physical storage location (e.g., an S3 bucket) that can hold multiple resources. Each shard has: + +- A `kind` (e.g., "vector" or "iceberg") +- A `shard_key` (the physical identifier, like "vector-shard-01") +- A `capacity` (maximum number of slots) +- A `next_slot` counter (tracks the next slot number to mint) +- A `status` ("active", "draining", or "disabled") + +### 2. Slots + +A **slot** is a numbered position within a shard. Slots are **sparse** - they only exist as rows in `shard_slots` when they've been allocated at least once. + +Each slot can be in one of three states: + +1. **Free**: `resource_id IS NULL` AND no active pending reservation exists +2. **Leased**: `resource_id IS NULL` AND an active pending reservation exists (temporarily held during creation) +3. **Confirmed**: `resource_id IS NOT NULL` (contains an active resource) + +### 3. Reservations + +A **reservation** is a time-bound claim on a slot. It's used to coordinate the two-phase process of creating a resource: + +1. **Reserve**: Claim a slot and insert metadata in the database +2. **Confirm**: Create the actual resource (e.g., S3 Vector index) and mark the reservation as confirmed + +Reservations have a `lease_expires_at` timestamp to prevent orphaned slots if a process crashes mid-creation. + +## The Reservation Flow + +### Phase 1: Reserve a Slot + +When a tenant wants to create a new resource (e.g., a vector index), the system: + +1. **Acquire advisory lock** on the logical key to prevent concurrent reservations +2. **Check for existing reservation** by `(kind, logical_key)` + - If pending or confirmed → return the existing reservation + - If cancelled or expired → delete it and continue +3. **Select a shard** using fullest first strategy +4. **Reserve a slot** on that shard (see "Slot Allocation" below) +5. **Create reservation record** in `shard_reservation` table +6. **Return reservation details** to the caller + +### Phase 2: Confirm the Reservation + +After the physical resource is created: + +1. **Update `shard_slots`**: Set `resource_id` +2. **Update `shard_reservation`**: Set status to `'confirmed'` + +This is done atomically to ensure consistency. + +### Phase 3: Cleanup on Failure + +If resource creation fails: + +1. **Cancel the reservation**: Set status to `'cancelled'` + +The slot becomes **free** again and can be reused immediately (since there's no active pending reservation). + +## Slot Allocation Algorithm + +When `reserveOneSlotOnShard(shardId, tenantId)` is called: + +### Step 1: Try to Claim a Free Existing Slot + +```sql +WITH pick AS ( + SELECT slot_no + FROM shard_slots ss + WHERE ss.shard_id = ? + AND ss.resource_id IS NULL -- Not occupied by a resource + AND NOT EXISTS ( + SELECT 1 FROM shard_reservation sr + WHERE sr.shard_id = ss.shard_id + AND sr.slot_no = ss.slot_no + AND sr.status = 'pending' + AND sr.lease_expires_at > now() + ) + ORDER BY slot_no + LIMIT 1 + FOR UPDATE SKIP LOCKED -- Lock-free concurrent access +) +UPDATE shard_slots s + SET tenant_id = ? +FROM pick +WHERE s.shard_id = ? AND s.slot_no = pick.slot_no +RETURNING s.slot_no; +``` + +**Why this works:** + +- Free slots have `resource_id` as `NULL` and no active pending reservation +- These slots were previously used but are now available for reuse +- `FOR UPDATE SKIP LOCKED` allows concurrent processes to skip locked rows and find other free slots + +### Step 2: Mint a Fresh Slot (if no free slots found) + +```sql +WITH ok AS ( + SELECT id, capacity, next_slot + FROM shard + WHERE id = ? AND status = 'active' +), +bumped AS ( + UPDATE shard + SET next_slot = ok.next_slot + 1 + FROM ok + WHERE shard.id = ok.id + AND ok.next_slot < ok.capacity -- Enforce capacity limit + RETURNING ok.next_slot AS slot_no +) +SELECT slot_no FROM bumped; +``` + +Then insert a new row in `shard_slots`: + +```sql +INSERT INTO shard_slots (shard_id, slot_no, tenant_id) +VALUES (?, ?, ?); +``` + +**Why we need `next_slot`:** + +- It ensures we never create duplicate slot numbers +- It tracks how many slots have ever been created (even if some are now free) +- It enforces the shard's capacity limit + +## How Reservations Prevent Double Allocation + +The `shard_reservation` table serves as the **source of truth** for active leases. Here's how the system prevents double allocation: + +### The Problem: Resource Creation is Not Atomic + +Creating a resource involves multiple steps: + +1. Reserve a slot in the database +2. Create the actual resource (e.g., call S3 Vectors API) +3. Update status to confirmed + +If a process crashes between steps 1 and 2, we'd have a slot that's "allocated" in the database but has no actual resource. + +### The Solution: Time-Bound Reservation Records + +1. **During allocation**: Create a reservation record with status `'pending'` and a `lease_expires_at` timestamp +2. **On success**: Set `resource_id` in the slot, mark reservation as `'confirmed'` +3. **On failure**: Mark reservation as `'cancelled'` (slot becomes free again) +4. **On timeout**: A background job marks expired reservations as `'expired'` automatically + +### The State Machine + +``` +FREE LEASED CONFIRMED +(no resource_id, (no resource_id, (resource_id set, + no active pending) pending reservation exists) confirmed reservation) + │ │ │ + ├────reserve slot───────────►│ │ + │ (create pending resv) │ │ + │ ├──confirm───────────────────►│ + │ │ (set resource_id, │ + │ │ mark resv confirmed) │ + │ │ │ + │◄───cancel/expire───────────┤ │ + │ (mark resv cancelled) │ │ + │ │ │ + │◄─────────────────delete resource────────────────────────┤ + │ (clear resource_id) │ +``` + +**How the system prevents double allocation:** + +- A slot is only considered "free" if there's no active pending reservation for it +- The `NOT EXISTS` query checks for pending reservations with unexpired leases +- Multiple processes cannot create duplicate pending reservations due to unique constraint on `(kind, logical_key)` +- The reservation table provides a full audit trail of all attempts + +### Example: Concurrent Allocations + +``` +Process A Process B +──────────────────────────────────────────────────── +1. Reserve slot 5 + INSERT INTO shard_reservation + (status='pending', slot_no=5) + +2. Start creating resource... + 3. Try to reserve slot 5 + WHERE NOT EXISTS ( + pending reservation for slot 5 + ) + → SKIP (pending exists!) + +3a. If success: + SET resource_id = 'res-A' + UPDATE reservation status='confirmed' + +3b. If failure: + UPDATE reservation status='cancelled' + 4. Try again, now succeeds: + WHERE NOT EXISTS ( + active pending for slot 5 + ) + → OK (cancelled, not pending!) + INSERT new reservation for slot 5 +``` + +Without checking for active pending reservations, both processes could claim the same slot simultaneously. + +### Database Design + +The relationship is: + +- `shard_reservation` has a foreign key to `shard_slots` via `(shard_id, slot_no)` +- This means **reservations point to slots** (not vice versa) +- A slot can have multiple reservation records over time (audit trail) +- Only one pending reservation per slot at a time is allowed by the NOT EXISTS check + +## Handling Old Reservations During Slot Reuse + +### The Challenge + +When a slot is freed (after a resource is deleted), we have: + +- A row in `shard_slots` with `resource_id = NULL` (slot is free) +- A row in `shard_reservation` with status `'confirmed'` (from the previous allocation) + +The slot is considered free because the NOT EXISTS check only looks for **pending** reservations with valid leases. However, when we try to create a new reservation for the same slot, we'd hit a unique constraint violation on `(shard_id, slot_no)` because the old confirmed reservation still exists. + +### The Solution: `deleteStaleReservationsForSlot()` + +Before inserting a new reservation, we call: + +```typescript +await store.deleteStaleReservationsForSlot(shardId, slotNo); +``` + +This deletes any old reservation rows (cancelled, expired, or confirmed) for the same `(shard_id, slot_no)` pair, allowing the slot to be reused with a new reservation. + +**Why we need this:** + +- Old reservations (cancelled, expired, or confirmed from freed slots) would prevent creating new reservations for the same slot +- Without cleanup, we'd get a unique violation on the `(shard_id, slot_no)` constraint +- Deleting old reservations before inserting enables slot reuse while preventing conflicts +- Only pending reservations are preserved (active leases in progress) + +## Tenant ID Tracking + +Both `shard_slots` and `shard_reservation` tables include a `tenant_id` column for: + +- **Usage accounting**: Count slots per tenant +- **Quota enforcement**: Limit resources per tenant +- **Billing**: Track resource consumption + +The `tenant_id` is: + +- Set in both `shard_slots` and `shard_reservation` when reserving a slot +- Preserved in `shard_slots` when confirming +- Cleared from `shard_slots` when freeing a slot for reuse + +### Available Strategies + +#### 1. Fill-First Strategy (Default) + +Prioritizes the shard with the **least free capacity** (most full first). This minimizes the number of active shards by filling them sequentially. + +**Implementation**: Uses a single efficient SQL query: + +```sql +SELECT s.*, + GREATEST( + (s.capacity - s.next_slot) + + COALESCE(( + SELECT COUNT(*) + FROM shard_slots sl + WHERE sl.shard_id = s.id + AND sl.resource_id IS NULL + AND NOT EXISTS ( + SELECT 1 FROM shard_reservation sr + WHERE sr.shard_id = sl.shard_id + AND sr.slot_no = sl.slot_no + AND sr.status = 'pending' + AND sr.lease_expires_at > now() + ) + ), 0), + 0 + ) AS free_capacity +FROM shard s +WHERE s.kind = ? AND s.status = 'active' +HAVING free_capacity > 0 +ORDER BY free_capacity ASC, s.shard_key ASC +LIMIT 1; +``` + +**Good for:** + +- Reducing operational costs (fewer active shards to manage) +- Low contention environments +- When deterministic placement isn't required +- Consolidating resources into fewer physical locations + +**Trade-offs:** + +- No consistency across retries (a resource could map to different shards if capacity changes) +- All new allocations go to the same shard until it fills up (potential write hotspot) + +## Capacity Management + +Each shard has a `capacity` limit. When a shard reaches capacity: + +1. `reserveOneSlotOnShard()` returns `null` +2. The shard selector may return a different shard (depending on strategy) +3. If no shards have available capacity, allocation fails with `NoActiveShardError` + +**Capacity calculation:** + +``` +available = (capacity - next_slot) + count(free_slots) +``` + +Where: + +- `capacity - next_slot` = slots that were never minted +- `count(free_slots)` = previously used slots that are now free + +## Lease Expiration + +A background job periodically calls `expireLeases()` to: + +1. Find reservations where `status = 'pending' AND lease_expires_at < now()` +2. Mark reservations as expired: `UPDATE shard_reservation SET status = 'expired'` + +Once marked as expired, slots automatically become free (since the NOT EXISTS check only looks for pending reservations with valid leases). This ensures that crashed processes don't permanently leak slots. + +## Consistency Guarantees + +### Advisory Locks + +The system uses PostgreSQL advisory locks (`pg_advisory_xact_lock`) to serialize operations on the same logical resource, preventing race conditions. + +**Scope**: Per logical resource (tenant + bucket + resource name) +**Protection**: Prevents duplicate reservations for the same logical resource + +### Transactions + +All multi-step operations are wrapped in database transactions with serializable isolation level to ensure consistency. + +### Atomic Confirmations + +The `confirmReservation()` method uses a CTE (Common Table Expression) to atomically: + +- Check the reservation is still valid +- Update the slot +- Update the reservation status + +If any step fails, the entire operation rolls back. + +### Handling Concurrent Shard Selection + +**The Problem**: Without locking, two processes might: + +1. Read the same `next_slot` value (uncommitted data) +2. Both try to allocate the same slot number +3. Violate fill-first ordering by reading stale capacity + +**The Solution**: `FOR UPDATE` on Shard Selection + +The shard selection query uses `FOR UPDATE` to lock shard rows: + +```sql +WITH candidates AS ( + SELECT s.*, + FROM shard s + WHERE s.kind = ? AND s.status = 'active' + FOR UPDATE -- ← Serialize selection on same shard +) +SELECT * FROM candidates +WHERE free_capacity > 0 +ORDER BY free_capacity ASC +LIMIT 1; +``` + +**How it works**: + +``` +Process A Process B +──────────────────────────────────────────────────── +SELECT ... FOR UPDATE +Locks shard-1 row +Returns shard-1 + SELECT ... FOR UPDATE + ⏸ Waits for shard-1 lock +reserveSlot(1) → SUCCESS +Updates next_slot: 0 → 1 +Commit → releases lock + ✅ Lock acquired! + Reads next_slot = 1 (committed) + Returns shard-1 + reserveSlot(1) → SUCCESS (slot 1) +``` + +**Why `FOR UPDATE` (not `SKIP LOCKED`)**: + +- ✅ **Serializes selection**: Processes wait for correct capacity +- ✅ **Maintains fill-first**: All processes try fullest shard first +- ✅ **Prevents stale reads**: Always see committed `next_slot` value +- ✅ **No race on next_slot**: Can't allocate duplicate slot numbers + +**Why NOT `SKIP LOCKED`**: + +- ❌ Would violate fill-first: Process B skips to shard-2 while shard-1 has space +- ❌ Would spread load prematurely: Defeats purpose of fill-first strategy + +**Lock Scope**: + +- Locks only shard rows (not slots) +- Lock duration: ~1ms (during SELECT only) +- Released on transaction commit +- Affects only processes selecting the **same** kind (e.g., all "vector" allocations) + +**Additional Protection**: `FOR UPDATE SKIP LOCKED` on Slots + +The `reserveOneSlotOnShard` query uses `FOR UPDATE SKIP LOCKED` on **slot rows**: + +```sql +SELECT ss.slot_no +FROM shard_slots ss +WHERE ss.shard_id = ? AND ss.resource_id IS NULL +FOR UPDATE SKIP LOCKED -- ← Skip locked slots +``` + +This allows **concurrent reservations on different slots** within the same shard. + +## Error Handling + +### UniqueViolationError + +If `insertReservation()` fails with a unique constraint violation: + +1. Check if another process created a valid reservation concurrently +2. If yes, return that reservation (idempotent) +3. If no, clear the lease and fail + +### Serialization Errors + +The vector metadata DB has retry logic for serialization errors: + +- Max 3 retries with exponential backoff +- Only retries on PostgreSQL error code `40001` (serialization failure) + +## Summary + +The sharding system provides: + +- **Efficient allocation**: Reuses free slots before minting new ones +- **Consistency**: Advisory locks and transactions prevent race conditions +- **Fault tolerance**: Lease expiration prevents permanent slot leaks +- **Scalability**: Sparse slot storage and lock-free concurrent access +- **Multi-tenancy**: Tenant tracking for accounting and quota enforcement +- **Audit trail**: Full history of all reservation attempts in `shard_reservation` table diff --git a/src/internal/sharding/errors.ts b/src/internal/sharding/errors.ts new file mode 100644 index 000000000..3606065b8 --- /dev/null +++ b/src/internal/sharding/errors.ts @@ -0,0 +1,34 @@ +export class NoActiveShardError extends Error { + constructor(kind: string) { + super(`No active shards for kind=${kind}`) + this.name = 'NoActiveShardError' + } +} + +export class NoCapacityError extends Error { + constructor() { + super('No capacity left on any active shard') + this.name = 'NoCapacityError' + } +} + +export class ReservationNotFoundError extends Error { + constructor() { + super('Reservation not found') + this.name = 'ReservationNotFoundError' + } +} + +export class InvalidReservationStatusError extends Error { + constructor(status: string) { + super(`Reservation status is ${status}`) + this.name = 'InvalidReservationStatusError' + } +} + +export class ExpiredReservationError extends Error { + constructor() { + super('Reservation lease expired or slot no longer held') + this.name = 'ExpiredReservationError' + } +} diff --git a/src/internal/sharding/index.ts b/src/internal/sharding/index.ts new file mode 100644 index 000000000..3f204a6c1 --- /dev/null +++ b/src/internal/sharding/index.ts @@ -0,0 +1,5 @@ +export * from './knex' +export * from './sharder' +export * from './store' +export * from './strategy/catalog' +export * from './strategy/single-shard' diff --git a/src/internal/sharding/knex.ts b/src/internal/sharding/knex.ts new file mode 100644 index 000000000..ff93730b2 --- /dev/null +++ b/src/internal/sharding/knex.ts @@ -0,0 +1,420 @@ +import { hashStringToInt } from '@internal/hashing' +import { Knex } from 'knex' +import { + ReservationRow, + ResourceKind, + ShardRow, + ShardStatus, + ShardStore, + ShardStoreFactory, + UniqueViolationError, +} from './store' + +export class KnexShardStoreFactory implements ShardStoreFactory { + constructor(private knex: Knex) {} + + withExistingTransaction(tnx: Knex.Transaction): ShardStoreFactory { + return new KnexShardStoreFactory(tnx) + } + async withTransaction(fn: (store: ShardStore) => Promise): Promise { + if (this.knex.isTransaction) { + // Already in a transaction, use current connection + return fn(new KnexShardStore(this.knex)) + } + + try { + return await this.knex.transaction(async (trx) => { + return fn(new KnexShardStore(trx)) + }) + } catch (error) { + throw error + } + } + autocommit(): ShardStore { + return new KnexShardStore(this.knex) + } +} + +class KnexShardStore implements ShardStore { + constructor(private db: Knex | Knex.Transaction) {} + + private q(sql: string, params?: any[]) { + return this.db.raw(sql, params as any) + } + + async findShardById(shardId: number): Promise { + const shard = await this.db('shard').select('*').where({ id: shardId }).first() + return shard ?? null + } + + async advisoryLockByString(key: string): Promise { + const id = hashStringToInt(key) + await this.q(`SELECT pg_advisory_xact_lock(?::bigint)`, [id]) + } + + async findShardByResourceId(tenantId: string, resourceId: string): Promise { + const result = await this.db + .select('s.shard_key', 's.id') + .from('shard_slots as ss') + .join('shard as s', 's.id', 'ss.shard_id') + .where('ss.resource_id', resourceId) + .where('ss.tenant_id', tenantId) + .first() + + return result ?? null + } + + async getOrInsertShard( + kind: ResourceKind, + shardKey: string, + capacity: number, + status: ShardStatus + ): Promise { + const inserted = await this.db('shard') + .insert({ kind, shard_key: shardKey, capacity, status, next_slot: 0 }) + .onConflict(['kind', 'shard_key']) + .ignore() + .returning('*') + if (inserted[0]) return inserted[0] + const row = await this.db('shard').where({ kind, shard_key: shardKey }).first() + if (!row) throw new Error('Failed to fetch shard after idempotent insert') + return row + } + + async setShardStatus(shardId: string | number, status: ShardStatus): Promise { + await this.db('shard').update({ status }).where({ id: shardId }) + } + + async listActiveShards(kind: ResourceKind): Promise { + return this.db('shard').select('*').where({ kind, status: 'active' }) + } + + async findShardWithLeastFreeCapacity(kind: ResourceKind): Promise { + const result = await this.q<{ rows: ShardRow[] }>( + ` + WITH candidates AS ( + SELECT s.*, + GREATEST( + (s.capacity - s.next_slot) + + COALESCE(( + SELECT COUNT(*) + FROM shard_slots sl + WHERE sl.shard_id = s.id + AND sl.resource_id IS NULL + AND NOT EXISTS ( + SELECT 1 FROM shard_reservation sr + WHERE sr.shard_id = sl.shard_id + AND sr.slot_no = sl.slot_no + AND sr.status = 'pending' + AND sr.lease_expires_at > now() + ) + ), 0), + 0 + ) AS free_capacity + FROM shard s + WHERE s.kind = ? AND s.status = 'active' + FOR UPDATE + ) + SELECT * + FROM candidates + WHERE free_capacity > 0 + ORDER BY free_capacity ASC, shard_key ASC + LIMIT 1; + `, + [kind] + ) + + return result.rows[0] ?? null + } + + async findReservationByKindKey( + tenantId: string, + kind: ResourceKind, + resourceId: string + ): Promise { + return ( + (await this.db('shard_reservation') + .select('shard_reservation.*', 'shard.shard_key as shard_key') + // @ts-expect-error join column added dynamically + .where({ 'shard_reservation.kind': kind, resource_id: resourceId }) + .andWhere('shard_reservation.tenant_id', tenantId) + .join('shard', 'shard.id', 'shard_reservation.shard_id') + .first()) ?? null + ) + } + + async fetchReservationById(id: string): Promise { + return ( + (await this.db('shard_reservation').select('*').where({ id }).first()) ?? null + ) + } + + /** + * Reserve one slot on a shard (single-table, no freelist): + * 1) Try to claim a previously used-but-now-free row. + * 2) If none, mint a fresh slot number by bumping shard.next_slot (bounded by capacity) and insert row. + * + * A slot is "free" if: + * - resource_id IS NULL (not confirmed) + * - AND no active pending reservation exists for it + */ + async reserveOneSlotOnShard(shardId: string | number, tenantId: string): Promise { + // 1) Try to claim a free existing row + const claimed = await this.q<{ rows: { slot_no: number }[] }>( + ` + WITH pick AS ( + SELECT ss.slot_no + FROM shard_slots ss + WHERE ss.shard_id = ? + AND ss.resource_id IS NULL + AND NOT EXISTS ( + SELECT 1 FROM shard_reservation sr + WHERE sr.shard_id = ss.shard_id + AND sr.slot_no = ss.slot_no + AND sr.status = 'pending' + AND sr.lease_expires_at > now() + ) + ORDER BY ss.slot_no + LIMIT 1 + FOR UPDATE SKIP LOCKED + ) + UPDATE shard_slots s + SET tenant_id = ? + FROM pick + WHERE s.shard_id = ? AND s.slot_no = pick.slot_no + RETURNING s.slot_no; + `, + [shardId, tenantId, shardId] + ) + if (claimed.rows.length) return claimed.rows[0].slot_no + + // 2) Mint a fresh slot_no by bumping shard.next_slot (bounded by capacity) + const minted = await this.q<{ rows: { slot_no: number }[] }>( + ` + WITH ok AS ( + SELECT id, capacity, next_slot + FROM shard + WHERE id = ? AND status = 'active' + ), + bumped AS ( + UPDATE shard + SET next_slot = ok.next_slot + 1 + FROM ok + WHERE shard.id = ok.id + AND ok.next_slot < ok.capacity + RETURNING ok.next_slot AS slot_no + ) + SELECT slot_no FROM bumped; + `, + [shardId] + ) + const slotNo = minted.rows[0]?.slot_no ?? null + if (slotNo == null) return null // at capacity or shard not active + + // Create the slot row + try { + await this.db('shard_slots').insert({ + shard_id: shardId, + slot_no: slotNo, + tenant_id: tenantId, + }) + return slotNo + } catch (e: any) { + if (e?.code === '23505') { + // Extremely rare race if another tx inserted the same slot first. Let caller try another shard/attempt. + return null + } + throw e + } + } + + async insertReservation(data: { + id: string + kind: ResourceKind + resourceId: string + tenantId: string + shardId: string | number + shardKey: string + slotNo: number + leaseMs: number + }): Promise<{ lease_expires_at: string }> { + try { + const row = await this.db('shard_reservation') + .insert({ + id: data.id, + kind: data.kind, + resource_id: data.resourceId, + tenant_id: data.tenantId, + shard_id: data.shardId, + slot_no: data.slotNo, + status: 'pending', + lease_expires_at: (this.db as any).raw(`now() + interval '${data.leaseMs} milliseconds'`), + }) + .returning(['lease_expires_at']) + + return row[0] + } catch (e: any) { + if (e?.code === '23505') throw new UniqueViolationError() + throw e + } + } + + /** Confirm atomically: pending+lease valid → mark slot resource_id + shard_reservation confirmed */ + async confirmReservation( + reservationId: string, + resourceId: string, + tenantId: string + ): Promise { + const res = await this.q( + ` + WITH ok AS ( + SELECT r.shard_id, r.slot_no + FROM shard_reservation r + WHERE r.id = ? + AND r.status = 'pending' + AND r.tenant_id = ? + AND r.lease_expires_at > now() + ), + upd_slots AS ( + UPDATE shard_slots s + SET resource_id = ? + FROM ok + WHERE s.shard_id = ok.shard_id + AND s.slot_no = ok.slot_no + RETURNING 1 + ) + UPDATE shard_reservation r + SET status = 'confirmed' + WHERE r.id = ? + AND EXISTS (SELECT 1 FROM upd_slots) + RETURNING 1; + `, + [reservationId, tenantId, resourceId, reservationId] + ) + return (res as any).rowCount ?? (res as any).rows.length + } + + async updateReservationStatus( + id: string, + status: 'confirmed' | 'cancelled' | 'expired' + ): Promise { + await this.db('shard_reservation') + .update({ status }) + .where({ id }) + .andWhere('status', '<>', status) + } + + async deleteReservation(id: string): Promise { + await this.db('shard_reservation').where({ id }).del() + } + + async deleteStaleReservationsForSlot(shardId: string | number, slotNo: number): Promise { + // Delete any old reservations for this slot (cancelled, expired, or confirmed) + // This allows the slot to be reused with a new reservation + await this.db('shard_reservation') + .where({ shard_id: shardId, slot_no: slotNo }) + .where((q) => { + q.whereIn('status', ['cancelled', 'expired']).orWhere((q2) => { + q2.whereIn('status', ['pending', 'cancelled', 'expired']).andWhere( + 'lease_expires_at', + '<', + this.db.fn.now() + ) + }) + }) + .del() + } + + async loadExpiredPendingReservations(): Promise { + return this.db('shard_reservation') + .select('*') + .where({ status: 'pending' }) + .andWhere('lease_expires_at', '<', this.db.fn.now()) + } + + async markReservationsExpired(ids: string[]): Promise { + if (!ids.length) return + await this.db('shard_reservation').update({ status: 'expired' }).whereIn('id', ids) + } + + async freeByLocation(shardId: string | number, slotNo: number): Promise { + // On delete of a confirmed resource, mark its row as reusable (clear resource_id and tenant_id) + await this.q( + ` + WITH shard_slots as ( + UPDATE shard_slots + SET resource_id = null, tenant_id = null + WHERE shard_id = ? AND slot_no = ? + RETURNING shard_id, slot_no + ), + deleted_reservations as ( + DELETE FROM shard_reservation + WHERE shard_id = ? AND slot_no = ? + ) + SELECT 1; + `, + [shardId, slotNo, shardId, slotNo] + ) + } + + async freeByResource(shardId: string | number, resourceId: string, tenantId: string) { + await this.q( + ` + WITH shard_slots AS ( + UPDATE shard_slots + SET resource_id = null, tenant_id = null + WHERE shard_id = ? AND resource_id = ? AND tenant_id = ? + RETURNING shard_id, slot_no + ), + deleted_reservations AS ( + DELETE FROM shard_reservation + WHERE shard_id = ? + AND resource_id = ? + AND tenant_id = ? + ) + SELECT 1; + `, + [shardId, resourceId, tenantId, shardId, resourceId, tenantId] + ) + } + + async shardStats(kind?: ResourceKind) { + const res = await this.q( + ` + SELECT s.id AS shard_id, s.shard_key, s.capacity, s.next_slot, + -- confirmed allocations + (SELECT COUNT(*) FROM shard_slots sl WHERE sl.shard_id = s.id AND sl.resource_id IS NOT NULL) AS used, + -- remaining capacity = (unused unminted capacity) + (existing free rows) + GREATEST( + (s.capacity - s.next_slot) + + COALESCE(( + SELECT COUNT(*) + FROM shard_slots sl + WHERE sl.shard_id = s.id + AND sl.resource_id IS NULL + AND NOT EXISTS ( + SELECT 1 FROM shard_reservation sr + WHERE sr.shard_id = sl.shard_id + AND sr.slot_no = sl.slot_no + AND sr.status = 'pending' + AND sr.lease_expires_at > now() + ) + ), 0), + 0 + ) AS free + FROM shard s + ${kind ? `WHERE s.kind = ?` : ``} + ORDER BY s.kind, s.shard_key; + `, + kind ? [kind] : [] + ) + + return (res as any).rows.map((r: any) => ({ + shardId: String(r.shard_id), + shardKey: r.shard_key, + capacity: Number(r.capacity), + used: Number(r.used), + free: Number(r.free), + })) + } +} diff --git a/src/internal/sharding/sharder.ts b/src/internal/sharding/sharder.ts new file mode 100644 index 000000000..0ba6bde59 --- /dev/null +++ b/src/internal/sharding/sharder.ts @@ -0,0 +1,39 @@ +import { ResourceKind, ShardRow, ShardStats, ShardStatus } from './store' + +export interface ShardResource { + kind: ResourceKind + tenantId: string + bucketName: string + logicalName: string +} + +export interface ReservationResult { + reservationId: string + shardId: string + shardKey: string + slotNo: number + leaseExpiresAt: string +} + +export interface Sharder { + createShard(opts: { + kind: ResourceKind + shardKey: string + capacity?: number + status?: ShardStatus + }): Promise + + setShardStatus(shardId: string | number, status: ShardStatus): Promise + + reserve(opts: ShardResource): Promise + confirm(reservationId: string, resource: ShardResource): Promise + cancel(reservationId: string): Promise + expireLeases(): Promise + freeByLocation(shardId: string | number, slotNo: number): Promise + freeByResource(shardId: string | number, resource: ShardResource): Promise + shardStats(kind?: ResourceKind): Promise + findShardByResourceId(param: ShardResource): Promise + listShardByKind(icebergTables: ResourceKind): Promise + + withTnx(tnx: unknown): Sharder +} diff --git a/src/internal/sharding/store.ts b/src/internal/sharding/store.ts new file mode 100644 index 000000000..ff152db2b --- /dev/null +++ b/src/internal/sharding/store.ts @@ -0,0 +1,115 @@ +export type ResourceKind = 'vector' | 'iceberg-table' +export type ShardStatus = 'active' | 'draining' | 'disabled' +export type ReservationStatus = 'pending' | 'confirmed' | 'expired' | 'cancelled' + +export type ShardRow = { + id: number + kind: ResourceKind + shard_key: string + capacity: number + next_slot: number + status: ShardStatus + created_at: string +} + +export type ReservationRow = { + id: string + kind: ResourceKind + resource_id: string + shard_id: string + shard_key: string + slot_no: number + status: ReservationStatus + tenant_id: string + lease_expires_at: string + created_at: string +} + +export type ShardStats = Array<{ + shardId: string + shardKey: string + capacity: number + used: number + free: number +}> + +/** Factory that opens a transaction and passes a store bound to that tx */ +export interface ShardStoreFactory { + withTransaction(fn: (store: ShardStore) => Promise): Promise + withExistingTransaction(tnx: Tnx): ShardStoreFactory + /** Optional: an autocommit store for read-only helpers */ + autocommit(): ShardStore +} + +export class UniqueViolationError extends Error { + constructor(message = 'unique_violation') { + super(message) + this.name = 'UniqueViolationError' + } +} + +export interface ShardStoreFactory { + withTransaction(fn: (store: ShardStore) => Promise): Promise + autocommit(): ShardStore // optional, for reads/one-off writes +} + +/** Every method uses the bound tx/connection internally */ +export interface ShardStore { + // Locks + advisoryLockByString(key: string): Promise + + // Shards + getOrInsertShard( + kind: ResourceKind, + shardKey: string, + capacity: number, + status: ShardStatus + ): Promise + setShardStatus(shardId: string | number, status: ShardStatus): Promise + listActiveShards(kind: ResourceKind): Promise + findShardWithLeastFreeCapacity(kind: ResourceKind): Promise + + // Reservations + findReservationByKindKey( + tenantId: string, + kind: ResourceKind, + resourceId: string + ): Promise + fetchReservationById(id: string): Promise + + // Sparse allocation (single-table, no freelist) + // No longer needs reservationId parameter since slots don't track it + reserveOneSlotOnShard(shardId: string | number, tenantId: string): Promise + + insertReservation(data: { + id: string + kind: ResourceKind + resourceId: string + tenantId: string + shardId: string | number + shardKey: string + slotNo: number + leaseMs: number + }): Promise<{ lease_expires_at: string }> + + /** Atomic confirm (checks: pending + lease valid) */ + confirmReservation(reservationId: string, resourceId: string, tenantId: string): Promise + + updateReservationStatus(id: string, status: 'confirmed' | 'cancelled' | 'expired'): Promise + deleteReservation(id: string): Promise + deleteStaleReservationsForSlot(shardId: string | number, slotNo: number): Promise + + // Expiry + loadExpiredPendingReservations(): Promise + markReservationsExpired(ids: string[]): Promise + + // Free-by-location after delete + freeByLocation(shardId: string | number, slotNo: number): Promise + freeByResource(shardId: string | number, resourceId: string, tenantId: string): Promise + findShardByResourceId(tenantId: string, resourceId: string): Promise + + // Stats + shardStats(kind?: ResourceKind): Promise + + findShardById(shardId: number): Promise +} diff --git a/src/internal/sharding/strategy/catalog.ts b/src/internal/sharding/strategy/catalog.ts new file mode 100644 index 000000000..d8d9a904d --- /dev/null +++ b/src/internal/sharding/strategy/catalog.ts @@ -0,0 +1,341 @@ +import { ERRORS } from '@internal/errors' +import { + ExpiredReservationError, + InvalidReservationStatusError, + NoActiveShardError, + ReservationNotFoundError, +} from '@internal/sharding/errors' +import { randomUUID } from 'crypto' +import { Sharder, ShardResource } from '../sharder' +import { + ResourceKind, + ShardRow, + ShardStatus, + ShardStoreFactory, + UniqueViolationError, +} from '../store' + +/** + * Represents the configuration options for a shard in a distributed system or database. + * + * @interface ShardOptions + * + * @property {ResourceKind} kind - The type of resource the shard is managing or related to. + * @property {string} shardKey - A unique identifier used to determine data placement within the shard. + * @property {number} [capacity] - Optional. The storage or operational capacity allocated to the shard. + * @property {ShardStatus} [status] - Optional. The current operational status of the shard. + */ +interface ShardOptions { + kind: ResourceKind + shardKey: string + capacity?: number + status?: ShardStatus +} + +/** + * Represents a catalog that manages shards and provides functionality for allocation, reservation, and management. + * This class uses transactions for consistent state management and interacts with a shard storage system via a factory. + */ +export class ShardCatalog implements Sharder { + constructor(private factory: ShardStoreFactory) {} + + withTnx(tnx: unknown) { + return new ShardCatalog(this.factory.withExistingTransaction(tnx)) + } + + /** + /** + * Creates a new shard or retrieves an existing one based on the provided options. + * + * @param opts - The options to configure the shard. Includes properties like kind, shardKey, capacity, and status. + * @return A promise that resolves to the created or retrieved shard row. + */ + async createShard(opts: ShardOptions): Promise { + const capacity = opts.capacity ?? 10_000 + + return this.factory.withTransaction(async (store) => { + return await store.getOrInsertShard( + opts.kind, + opts.shardKey, + capacity, + opts.status ?? 'active' + ) + }) + } + + /** + /** + * Creates multiple shards based on the provided shard options. + * + * @param opts - An array of shard options to configure each shard. + * @return A promise that resolves to an array of created shards. + */ + createShards(opts: ShardOptions[]) { + return Promise.all(opts.map((o) => this.createShard(o))) + } + + /** + /** + * Updates the status of a shard. + * + * @param shardId - The unique identifier of the shard to update. + * @param status - The new status to set for the shard. + * @return A promise that resolves when the status is updated. + */ + async setShardStatus(shardId: string | number, status: ShardStatus) { + return this.factory.withTransaction((store) => store.setShardStatus(shardId, status)) + } + + /** + * Reserves a slot on a shard for a specific resource. + * If a reservation already exists for the same resource, returns the existing reservation. + * Uses advisory locking to prevent race conditions. + * + * @param opts - The reservation options. + * @param opts.kind - The type of resource being reserved. + * @param opts.tenantId - The ID of the tenant making the reservation. + * @param opts.bucketName - The name of the bucket containing the resource. + * @param opts.logicalName - The logical name of the resource. + * @param opts.leaseMs - Optional. The lease duration in milliseconds (default: 60000ms). + * @return A promise that resolves to an object containing reservation details. + * @return return.reservationId - The unique identifier for the reservation. + * @return return.shardId - The ID of the shard where the slot was reserved. + * @return return.shardKey - The key of the shard where the slot was reserved. + * @return return.slotNo - The slot number that was reserved. + * @return return.leaseExpiresAt - The ISO timestamp when the lease expires. + * @throws NoActiveShardError if no active shard is available for the resource kind. + */ + async reserve(opts: { + kind: ResourceKind + tenantId: string + bucketName: string + logicalName: string + leaseMs?: number + shardId?: number + }): Promise<{ + reservationId: string + shardId: string + shardKey: string + slotNo: number + leaseExpiresAt: string + }> { + const leaseMs = opts.leaseMs ?? 60_000 + const resourceId = `${opts.kind}::${opts.bucketName}::${opts.logicalName}` + + return this.factory.withTransaction(async (store) => { + await store.advisoryLockByString(resourceId) + + const existing = await store.findReservationByKindKey(opts.tenantId, opts.kind, resourceId) + + if (existing) { + if (existing.status === 'pending' || existing.status === 'confirmed') { + return { + shardKey: existing.shard_key, + reservationId: existing.id, + shardId: String(existing.shard_id), + slotNo: Number(existing.slot_no), + leaseExpiresAt: existing.lease_expires_at, + } + } + + // If cancelled or expired, delete it so we can create a new reservation + if ( + existing.status === 'cancelled' || + existing.status === 'expired' || + new Date(existing.lease_expires_at) < new Date() + ) { + await store.deleteReservation(existing.id) + } + } + + const reservationId = randomUUID() + + // Select shard using FOR UPDATE to serialize selection and ensure + // we read the committed next_slot value + const shard = opts.shardId + ? await store.findShardById(opts.shardId) + : await store.findShardWithLeastFreeCapacity(opts.kind) + if (!shard) { + throw new NoActiveShardError(opts.kind) + } + + // Reserve a slot on the selected shard + // FOR UPDATE ensures no two processes reserve on the same shard simultaneously + const slotNo = await store.reserveOneSlotOnShard(shard.id, opts.tenantId) + if (slotNo == null) { + // This should be very rare since FOR UPDATE serializes selection + // Only happens if shard fills up between selection and reservation + throw new NoActiveShardError(opts.kind) + } + + await store.deleteStaleReservationsForSlot(shard.id, slotNo) + + try { + const { lease_expires_at } = await store.insertReservation({ + id: reservationId, + kind: opts.kind, + resourceId, + tenantId: opts.tenantId, + shardId: shard.id, + shardKey: shard.shard_key, + slotNo, + leaseMs, + }) + + return { + reservationId, + shardId: String(shard.id), + shardKey: shard.shard_key, + slotNo, + leaseExpiresAt: lease_expires_at, + } + } catch (e) { + if (e instanceof UniqueViolationError) { + throw ERRORS.ResourceAlreadyExists(e) + } + throw e + } + }) + } + + /** + * Confirms a pending reservation and associates it with a resource. + * If the reservation has expired, frees the slot and throws an error. + * + * @param reservationId - The unique identifier of the reservation to confirm. + * @param resource - The resource details to associate with the reservation. + * @param resource.kind - The type of resource. + * @param resource.tenantId - The ID of the tenant owning the resource. + * @param resource.bucketName - The name of the bucket containing the resource. + * @param resource.logicalName - The logical name of the resource. + * @return A promise that resolves when the reservation is confirmed. + * @throws ReservationNotFoundError if the reservation does not exist. + * @throws InvalidReservationStatusError if the reservation is not in a pending state. + * @throws ExpiredReservationError if the reservation lease has expired. + */ + async confirm( + reservationId: string, + resource: { + kind: ResourceKind + tenantId: string + bucketName: string + logicalName: string + } + ): Promise { + await this.factory.withTransaction(async (store) => { + const resv = await store.fetchReservationById(reservationId) + if (!resv) throw new ReservationNotFoundError() + if (resv.status === 'confirmed') return + + const resourceId = `${resource.kind}::${resource.bucketName}::${resource.logicalName}` + + const ok = await store.confirmReservation(reservationId, resourceId, resource.tenantId) + + if (!ok) { + const fresh = await store.fetchReservationById(reservationId) + + if (!fresh) { + throw new ReservationNotFoundError() + } + + if (fresh.status !== 'pending') { + throw new InvalidReservationStatusError(fresh.status) + } + + await this.freeByLocation(fresh.shard_id, fresh.slot_no) + + throw new ExpiredReservationError() + } + }) + } + + /** + * Cancels a pending reservation. + * If the reservation does not exist, the operation completes silently. + * + * @param reservationId - The unique identifier of the reservation to cancel. + * @return A promise that resolves when the reservation is cancelled. + */ + async cancel(reservationId: string): Promise { + await this.factory.withTransaction(async (store) => { + const resv = await store.fetchReservationById(reservationId) + if (!resv) return + await store.updateReservationStatus(reservationId, 'cancelled') + }) + } + + /** + * Expires all pending reservations whose lease has expired. + * + * @return A promise that resolves to the number of reservations that were expired. + */ + async expireLeases(): Promise { + return this.factory.withTransaction(async (store) => { + const expired = await store.loadExpiredPendingReservations() + if (!expired.length) return 0 + await store.markReservationsExpired(expired.map((r) => r.id)) + return expired.length + }) + } + + /** + * Frees a slot on a shard by its location (shard ID and slot number). + * + * @param shardId - The unique identifier of the shard. + * @param slotNo - The slot number to free. + * @return A promise that resolves when the slot is freed. + */ + freeByLocation(shardId: string | number, slotNo: number) { + return this.factory.autocommit().freeByLocation(shardId, slotNo) + } + + /** + * Frees a slot on a shard by resource identifier. + * + * @param shardId - The unique identifier of the shard. + * @param resource - The resource details to identify the slot. + * @param resource.kind - The type of resource. + * @param resource.bucketName - The name of the bucket containing the resource. + * @param resource.logicalName - The logical name of the resource. + * @param resource.tenantId - The ID of the tenant owning the resource. + * @return A promise that resolves when the slot is freed. + */ + freeByResource(shardId: string | number, resource: ShardResource): Promise { + const resourceId = `${resource.kind}::${resource.bucketName}::${resource.logicalName}` + return this.factory.autocommit().freeByResource(shardId, resourceId, resource.tenantId) + } + + /** + * Retrieves statistics for shards, optionally filtered by resource kind. + * + * @param kind - Optional. The resource kind to filter statistics by. + * @return A promise that resolves to an array of shard statistics. + */ + shardStats(kind?: ResourceKind) { + return this.factory.autocommit().shardStats(kind) + } + + /** + * Finds the shard associated with a specific resource. + * + * @param param - The resource identifier parameters. + * @param param.kind - The type of resource. + * @param param.tenantId - The ID of the tenant owning the resource. + * @param param.logicalName - The logical name of the resource. + * @param param.bucketName - The name of the bucket containing the resource. + * @return A promise that resolves to the shard row if found, or null if not found. + */ + async findShardByResourceId(param: { + kind: string + tenantId: string + logicalName: string + bucketName: string + }) { + const resourceId = `${param.kind}::${param.bucketName}::${param.logicalName}` + return this.factory.autocommit().findShardByResourceId(param.tenantId, resourceId) + } + + async listShardByKind(icebergTables: ResourceKind) { + return this.factory.autocommit().listActiveShards(icebergTables) + } +} diff --git a/src/internal/sharding/strategy/single-shard.ts b/src/internal/sharding/strategy/single-shard.ts new file mode 100644 index 000000000..1b8e8345d --- /dev/null +++ b/src/internal/sharding/strategy/single-shard.ts @@ -0,0 +1,107 @@ +import { ResourceKind, ShardRow, ShardStats, ShardStatus } from '@internal/sharding/store' +import { ReservationResult, Sharder, ShardResource } from '../sharder' + +export class SingleShard implements Sharder { + constructor( + protected readonly singleShard: { + shardKey: string + capacity: number + } + ) {} + + listShardByKind(_kind: ResourceKind): Promise { + return Promise.resolve([ + { + id: 1, + kind: 'iceberg-table', + shard_key: this.singleShard.shardKey, + capacity: this.singleShard.capacity, + next_slot: -1, + status: 'active', + created_at: new Date().toISOString(), + }, + ]) + } + + shardStats(_kind?: ResourceKind): Promise { + return Promise.resolve([ + { + shardId: '1', + shardKey: this.singleShard.shardKey, + capacity: this.singleShard.capacity, + used: -1, + free: -1, + }, + ]) + } + + withTnx(_tnx: unknown): Sharder { + return new SingleShard({ + shardKey: this.singleShard.shardKey, + capacity: this.singleShard.capacity, + }) + } + + freeByResource(_shardId: string | number, _resource: ShardResource): Promise { + return Promise.resolve() + } + + cancel(_reservationId: string): Promise { + return Promise.resolve(undefined) + } + + confirm(_reservationId: string, _resource: ShardResource): Promise { + return Promise.resolve(undefined) + } + + createShard(opts: { + kind: ResourceKind + shardKey: string + capacity?: number + status?: ShardStatus + }): Promise { + return Promise.resolve({ + shard_key: opts.shardKey, + capacity: opts.capacity || this.singleShard.capacity, + kind: opts.kind, + id: 1, + status: 'active', + next_slot: 1, + created_at: new Date().toISOString(), + }) + } + + expireLeases(): Promise { + return Promise.resolve(0) + } + + findShardByResourceId(param: ShardResource): Promise { + return Promise.resolve({ + id: 1, + kind: param.kind, + shard_key: this.singleShard.shardKey, + capacity: this.singleShard.capacity, + status: 'active', + next_slot: 1, + created_at: new Date().toISOString(), + }) + } + + freeByLocation(_shardId: string | number, _slotNo: number): Promise { + return Promise.resolve(undefined) + } + + reserve(_opts: ShardResource): Promise { + return Promise.resolve({ + leaseExpiresAt: '', + reservationId: '', + shardId: '1', + shardKey: this.singleShard.shardKey, + slotNo: 0, + }) + } + + setShardStatus(_shardId: string | number, _status: ShardStatus): Promise { + return Promise.resolve(undefined) + } +} diff --git a/src/internal/streams/byte-counter.ts b/src/internal/streams/byte-counter.ts index 0200c364e..424559540 100644 --- a/src/internal/streams/byte-counter.ts +++ b/src/internal/streams/byte-counter.ts @@ -17,3 +17,13 @@ export const createByteCounterStream = () => { }, } } + +export class RequestByteCounterStream extends Transform { + public receivedEncodedLength = 0 + + _transform(chunk: Buffer, _enc: BufferEncoding, cb: TransformCallback): void { + this.receivedEncodedLength += chunk.length + + cb(null, chunk) + } +} diff --git a/src/internal/streams/hash-stream.test.ts b/src/internal/streams/hash-stream.test.ts new file mode 100644 index 000000000..a798a4c78 --- /dev/null +++ b/src/internal/streams/hash-stream.test.ts @@ -0,0 +1,590 @@ +import { createHash } from 'node:crypto' +import fs from 'node:fs' +import * as fsp from 'node:fs/promises' +import os from 'node:os' +import path from 'node:path' +import { Readable, Writable } from 'node:stream' +import { pipeline } from 'node:stream/promises' +import { HashSpillWritable } from '@internal/streams/hash-stream' +import { vi } from 'vitest' +import { waitForEventually } from '../../test/utils/promise' + +function randBuf(size: number): Buffer { + const b = Buffer.allocUnsafe(size) + for (let i = 0; i < size; i++) b[i] = (i * 131 + 17) & 0xff // deterministic-ish + return b +} + +function readableFrom(...chunks: Buffer[]): Readable { + return Readable.from(chunks) +} + +async function dirEntries(p: string): Promise { + try { + const names = await fsp.readdir(p) + return names + } catch { + return [] + } +} + +async function countHashspillDirs(root: string): Promise { + const names = await dirEntries(root) + return names.filter((n) => n.startsWith('hashspill-')).length +} + +async function findSpillFilePath(root: string): Promise { + try { + const entries = await fsp.readdir(root, { withFileTypes: true }) + const dir = entries.find((e) => e.isDirectory() && e.name.startsWith('hashspill-')) + if (!dir) return null + const dirPath = path.join(root, dir.name) + const files = await fsp.readdir(dirPath) + if (files.length === 0) return null + return path.join(dirPath, files[0]) // our class writes a single file + } catch { + return null + } +} + +async function waitForHashspillDirs(root: string, expected: number): Promise { + return waitForEventually( + () => countHashspillDirs(root), + (count) => count === expected, + `${expected} hashspill dirs under ${root}`, + 5000, + 20 + ) +} + +class SlowWritable extends Writable { + private delayMs: number + constructor(delayMs = 5) { + super({ highWaterMark: 16 * 1024 }) // small HWM to induce backpressure + this.delayMs = delayMs + } + _write(chunk: Buffer, _enc: BufferEncoding, cb: (e?: Error | null) => void) { + setTimeout(() => cb(), this.delayMs) + } +} + +describe('HashSpillWritable', () => { + let tmpRoot: string + + beforeEach(async () => { + tmpRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'hsw-tests-')) + }) + + afterEach(async () => { + // best-effort cleanup of left-overs + try { + await fsp.rm(tmpRoot, { recursive: true, force: true }) + } catch {} + }) + + test('in-memory: under limit stays in memory; digest & size are correct', async () => { + const limit = 1024 * 64 + const payload = randBuf(limit - 7) + const expectedDigest = createHash('sha256').update(payload).digest('hex') + + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + await pipeline(readableFrom(payload), sink) + + expect(sink.size()).toBe(payload.length) + expect(sink.digestHex()).toBe(expectedDigest) + + // toReadable returns in-memory stream + const collected: Buffer[] = [] + await pipeline( + sink.toReadable(), + new Writable({ + write(chunk, _enc, cb) { + collected.push(chunk as Buffer) + cb() + }, + }) + ) + expect(Buffer.concat(collected)).toEqual(payload) + + // No hashspill-* dirs should have been created + expect(await countHashspillDirs(tmpRoot)).toBe(0) + + // cleanup() should be a no-op + await expect(sink.cleanup()).resolves.toBeUndefined() + }) + + test('in-memory: exactly at limit does not spill', async () => { + const limit = 32 * 1024 + const payload = randBuf(limit) + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + + await pipeline(readableFrom(payload), sink) + expect(sink.size()).toBe(limit) + expect(await countHashspillDirs(tmpRoot)).toBe(0) + }) + + test('spill: just over limit triggers spill; autoCleanup on reader removes artifacts', async () => { + const limit = 1024 * 32 + const payload = randBuf(limit + 1) + const expectedDigest = createHash('sha256').update(payload).digest('hex') + + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + await pipeline(readableFrom(payload), sink) + + expect(sink.digestHex()).toBe(expectedDigest) + // A spill should have created exactly one temp dir + expect(await countHashspillDirs(tmpRoot)).toBe(1) + + // Read with autoCleanup so artifacts get removed when last reader ends + await pipeline( + sink.toReadable({ autoCleanup: true }), + new Writable({ + write(_c, _e, cb) { + cb() + }, + }) + ) + + // The hashspill dir should be gone + await expect(waitForHashspillDirs(tmpRoot, 0)).resolves.toBe(0) + }) + + test('spill: multiple readers, autoCleanup waits for the last reader', async () => { + const limit = 8 * 1024 + const payload = randBuf(limit * 3) // force spill + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + + await pipeline(readableFrom(payload), sink) + expect(await countHashspillDirs(tmpRoot)).toBe(1) + + const r1 = sink.toReadable({ autoCleanup: true }) + const r2 = sink.toReadable({ autoCleanup: true }) + + // pipe r1 quickly + const fastConsumer = new Writable({ + write(_c, _e, cb) { + cb() + }, + }) + // r2 is slower + const slowConsumer = new SlowWritable(3) + + const p1 = pipeline(r1, fastConsumer) + const p2 = pipeline(r2, slowConsumer) + await Promise.all([p1, p2]) + + await expect(waitForHashspillDirs(tmpRoot, 0)).resolves.toBe(0) + }) + + test('manual cleanup: delete after readers close (call cleanup after reading)', async () => { + const limit = 4096 + const payload = randBuf(limit * 5) + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + + await pipeline(readableFrom(payload), sink) + expect(await countHashspillDirs(tmpRoot)).toBe(1) + + // No autoCleanup; we clean manually after + const r = sink.toReadable() + await pipeline( + r, + new Writable({ + write(_c, _e, cb) { + cb() + }, + }) + ) + + // Now manual cleanup removes artifacts + await sink.cleanup() + expect(await countHashspillDirs(tmpRoot)).toBe(0) + }) + + test('backpressure respected with slow downstream while hashing', async () => { + const limit = 16 * 1024 + const pieces = Array.from({ length: 50 }, (_, i) => randBuf(2048 + (i % 5))) + const payload = Buffer.concat(pieces) + const expectedDigest = createHash('sha256').update(payload).digest('hex') + + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + + // Write into sink, then read out to a slow consumer to ensure stream semantics hold + await pipeline(Readable.from(pieces), sink) + await pipeline( + sink.toReadable(), + new SlowWritable(2).on('pipe', function () {}) + ) + + expect(sink.digestHex()).toBe(expectedDigest) + }) + + test('multiple concurrent instances (no collisions, all succeed)', async () => { + const N = 8 + const limit = 8 * 1024 + const jobs = Array.from({ length: N }, async (_, idx) => { + const buf = randBuf(limit + 1024 + idx) // force spill + const exp = createHash('sha256').update(buf).digest('hex') + + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + await pipeline(readableFrom(buf), sink) + + expect(sink.digestHex()).toBe(exp) + // Use autoCleanup to clean right after reading + await pipeline( + sink.toReadable({ autoCleanup: true }), + new Writable({ + write(_c, _e, cb) { + cb() + }, + }) + ) + }) + + await Promise.all(jobs) + + await expect(waitForHashspillDirs(tmpRoot, 0)).resolves.toBe(0) + }) + + test('size() tracks total bytes written', async () => { + const limit = 10 * 1024 + const parts = [randBuf(1111), randBuf(2222), randBuf(3333)] + const total = parts.reduce((n, b) => n + b.length, 0) + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + + await pipeline(Readable.from(parts), sink) + expect(sink.size()).toBe(total) + }) + + test('toReadable() can be called multiple times (consistent replay)', async () => { + const limit = 4096 + const payload = randBuf(limit * 2) // spill + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + await pipeline(readableFrom(payload), sink) + + const readAll = async () => { + const chunks: Buffer[] = [] + await pipeline( + sink.toReadable(), + new Writable({ + write(c, _e, cb) { + chunks.push(c as Buffer) + cb() + }, + }) + ) + return Buffer.concat(chunks) + } + + const a = await readAll() + const b = await readAll() + expect(a).toEqual(payload) + expect(b).toEqual(payload) + + await sink.cleanup() + }) + + test('cleanup is a no-op for non-spilled streams', async () => { + const limit = 1 << 20 + const payload = randBuf(12345) // under limit + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + await pipeline(readableFrom(payload), sink) + await expect(sink.cleanup()).resolves.toBeUndefined() + // Nothing created on disk + expect(await countHashspillDirs(tmpRoot)).toBe(0) + }) + + test('errors if digestHex() is called before finish', async () => { + const limit = 1024 + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + + // start write but don't finish + const r = new Readable({ + read() { + this.push(randBuf(200)) + this.push(null) + }, + }) + await pipeline(r, sink) + // now finished — valid + expect(() => sink.digestHex()).not.toThrow() + }) + + test('spill: if file cannot be created/written, pipeline rejects with a handled error', async () => { + const limit = 8 * 1024 + const payload = randBuf(limit * 3) // force spill + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + + // Stub createWriteStream to fail on creation + const spy = vi.spyOn(fs, 'createWriteStream').mockImplementation(() => { + throw Object.assign(new Error('simulated createWriteStream failure'), { code: 'EACCES' }) + }) + + try { + await expect(pipeline(readableFrom(payload), sink)).rejects.toThrow( + /createWriteStream failure|EACCES|simulated/i + ) + } finally { + spy.mockRestore() + } + + await expect(waitForHashspillDirs(tmpRoot, 0)).resolves.toBe(0) + }) + + test('spill: spilled file exists before read and is deleted after autoCleanup', async () => { + const limit = 16 * 1024 + const payload = randBuf(limit * 2 + 123) // force spill + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + + await pipeline(readableFrom(payload), sink) + + // The spill dir & file should exist now + const prePath = await findSpillFilePath(tmpRoot) + expect(prePath).not.toBeNull() + expect(fs.existsSync(prePath!)).toBe(true) + + // Read with autoCleanup + await pipeline( + sink.toReadable({ autoCleanup: true }), + new Writable({ + write(_c, _e, cb) { + cb() + }, + }) + ) + + const cleanupState = await waitForEventually( + async () => ({ + postPath: await findSpillFilePath(tmpRoot), + prePathExists: fs.existsSync(prePath!), + dirCount: await countHashspillDirs(tmpRoot), + }), + (state) => state.postPath === null && !state.prePathExists && state.dirCount === 0, + `spill artifacts for ${prePath} to be removed`, + 5000, + 20 + ) + + expect(cleanupState.postPath).toBeNull() + expect(cleanupState.prePathExists).toBe(false) + expect(cleanupState.dirCount).toBe(0) + }) + test('concurrent spill operations: no temp file name collisions with rapid creation', async () => { + const limit = 4 * 1024 + const N = 20 // More instances to increase collision probability + + // Create all instances simultaneously to maximize collision chance + const sinks = Array.from( + { length: N }, + () => new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + ) + + // Start all writes concurrently + const writePromises = sinks.map(async (sink, idx) => { + const buf = randBuf(limit + 100 + idx) // force spill on all + await pipeline(readableFrom(buf), sink) + return { sink, expected: createHash('sha256').update(buf).digest('hex') } + }) + + const results = await Promise.all(writePromises) + + // Verify all succeeded with correct digests + for (const { sink, expected } of results) { + expect(sink.digestHex()).toBe(expected) + } + + // Verify all created separate temp directories + expect(await countHashspillDirs(tmpRoot)).toBe(N) + + // Clean up all with autoCleanup + const readPromises = results.map(({ sink }) => + pipeline( + sink.toReadable({ autoCleanup: true }), + new Writable({ + write(_c, _e, cb) { + cb() + }, + }) + ) + ) + + await Promise.all(readPromises) + + await expect(waitForHashspillDirs(tmpRoot, 0)).resolves.toBe(0) + }) + + test('concurrent spill with identical timestamps: UUID ensures uniqueness', async () => { + const limit = 2 * 1024 + const N = 10 + + // Mock Date.now to return same timestamp for all instances + const fixedTimestamp = 1234567890123 + vi.spyOn(Date, 'now').mockReturnValue(fixedTimestamp) + + try { + const jobs = Array.from({ length: N }, async (_, idx) => { + const payload = randBuf(limit + 50 + idx) + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + await pipeline(readableFrom(payload), sink) + + // Verify file was created successfully despite same timestamp + const spillPath = await findSpillFilePath(tmpRoot) + expect(spillPath).not.toBeNull() + + await sink.cleanup() + return sink.digestHex() + }) + + // All should succeed despite identical timestamps + const digests = await Promise.all(jobs) + expect(digests).toHaveLength(N) + + await expect(waitForHashspillDirs(tmpRoot, 0)).resolves.toBe(0) + } finally { + vi.restoreAllMocks() + } + }) + + test('concurrent readers on same spilled stream with mixed cleanup strategies', async () => { + const limit = 8 * 1024 + const payload = randBuf(limit * 2) + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + + await pipeline(readableFrom(payload), sink) + expect(await countHashspillDirs(tmpRoot)).toBe(1) + + // Create multiple readers: some with autoCleanup, some without + const readers = [ + { stream: sink.toReadable({ autoCleanup: true }), name: 'auto1' }, + { stream: sink.toReadable({ autoCleanup: false }), name: 'manual1' }, + { stream: sink.toReadable({ autoCleanup: true }), name: 'auto2' }, + { stream: sink.toReadable({ autoCleanup: false }), name: 'manual2' }, + { stream: sink.toReadable({ autoCleanup: true }), name: 'auto3' }, + ] + + // Read from all concurrently with varying speeds + const readPromises = readers.map(({ stream, name }, idx) => { + const consumer = new SlowWritable(100 * idx + 1) // Different speeds + return pipeline(stream, consumer) + }) + + await Promise.all(readPromises) + + // Cleanup should happen once the final autoCleanup reader finishes, + // even if some concurrent readers did not request autoCleanup. + await expect(waitForHashspillDirs(tmpRoot, 0)).resolves.toBe(0) + + await expect(sink.cleanup()).resolves.toBeUndefined() + }) + + test('rapid spill/cleanup cycles: no resource leaks or race conditions', async () => { + const limit = 4 * 1024 + const cycles = 15 + + for (let i = 0; i < cycles; i++) { + const payload = randBuf(limit + 100 + i) + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + + await pipeline(readableFrom(payload), sink) + + // Immediately read and cleanup + await pipeline( + sink.toReadable({ autoCleanup: true }), + new Writable({ + write(_c, _e, cb) { + cb() + }, + }) + ) + } + + await expect(waitForHashspillDirs(tmpRoot, 0)).resolves.toBe(0) + }) + + test('spill during concurrent writes to different tmp roots: isolation verified', async () => { + const limit = 6 * 1024 + const tmpRoot2 = await fsp.mkdtemp(path.join(os.tmpdir(), 'hsw-tests2-')) + + try { + const payload1 = randBuf(limit + 200) + const payload2 = randBuf(limit + 300) + + const sink1 = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + const sink2 = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot: tmpRoot2 }) + + // Write to both concurrently + await Promise.all([ + pipeline(readableFrom(payload1), sink1), + pipeline(readableFrom(payload2), sink2), + ]) + + // Each should have created temp dirs in their respective roots + expect(await countHashspillDirs(tmpRoot)).toBe(1) + expect(await countHashspillDirs(tmpRoot2)).toBe(1) + + // Cleanup both + await Promise.all([sink1.cleanup(), sink2.cleanup()]) + + expect(await countHashspillDirs(tmpRoot)).toBe(0) + expect(await countHashspillDirs(tmpRoot2)).toBe(0) + } finally { + await fsp.rm(tmpRoot2, { recursive: true, force: true }).catch(() => {}) + } + }) + + test('stress test: many concurrent spills with overlapping lifecycles', async () => { + const limit = 8 * 1024 + const batchSize = 12 + + // Create overlapping batches + const batch1Promise = Promise.all( + Array.from({ length: batchSize }, async (_, i) => { + const payload = randBuf(limit * 2 + i) + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + await pipeline(readableFrom(payload), sink) + + // Delay before reading to create overlap with batch2 + await new Promise((r) => setTimeout(r, 10 + (i % 3))) + + await pipeline( + sink.toReadable({ autoCleanup: true }), + new Writable({ + write(_c, _e, cb) { + cb() + }, + }) + ) + + return sink.digestHex() + }) + ) + + // Start second batch while first is still running + await new Promise((r) => setTimeout(r, 20)) + + const batch2Promise = Promise.all( + Array.from({ length: batchSize }, async (_, i) => { + const payload = randBuf(limit * 3 + i) + const sink = new HashSpillWritable({ limitInMemoryBytes: limit, tmpRoot }) + await pipeline(readableFrom(payload), sink) + + await pipeline( + sink.toReadable({ autoCleanup: true }), + new Writable({ + write(_c, _e, cb) { + cb() + }, + }) + ) + + return sink.digestHex() + }) + ) + + const [results1, results2] = await Promise.all([batch1Promise, batch2Promise]) + + expect(results1).toHaveLength(batchSize) + expect(results2).toHaveLength(batchSize) + + await expect(waitForHashspillDirs(tmpRoot, 0)).resolves.toBe(0) + }) +}) diff --git a/src/internal/streams/hash-stream.ts b/src/internal/streams/hash-stream.ts new file mode 100644 index 000000000..717be9a1b --- /dev/null +++ b/src/internal/streams/hash-stream.ts @@ -0,0 +1,267 @@ +// HashSpillWritable.ts + +import { createHash, randomUUID } from 'node:crypto' +import fs, { WriteStream } from 'node:fs' +import * as fsp from 'node:fs/promises' +import os from 'node:os' +import path from 'node:path' +import { Readable, Writable, WritableOptions } from 'node:stream' +import { finished } from 'node:stream/promises' + +export interface HashSpillWritableOptions { + /** Max bytes to keep in memory before spilling to disk (required, > 0). */ + limitInMemoryBytes: number + /** Hash algorithm (default: 'sha256'). */ + alg?: string + /** Parent directory for temp dirs (default: os.tmpdir()). */ + tmpRoot?: string + /** Writable options to pass to base class (rarely needed). */ + writableOptions?: WritableOptions +} + +export interface ToReadableOptions { + /** + * If true and data spilled to disk, the spilled file/dir will be removed + * after the **last** reader closes/ends. + */ + autoCleanup?: boolean +} + +/** + * Writable that hashes all bytes and buffers in memory up to `limitBytes`. + * On first overflow, it spills to a unique temp file and appends subsequent data there. + * - Call `digestHex()` *after* 'finish' (e.g. after pipeline resolves). + * - Get a fresh readable with `toReadable({ autoCleanup })`. If multiple readers + * are created, cleanup is deferred until the last one finishes. + * - Call `cleanup()` to explicitly remove temp artifacts; it defers until readers close. + */ +export class HashSpillWritable extends Writable { + private readonly limitBytes: number + private readonly alg: string + private readonly tmpRoot: string + + // Hashing + private hash = createHash('sha256') + + // Memory buffer until first spill + private chunks: Buffer[] | null = [] + private memSize = 0 + + // Spill state + private spilled = false + private tmpDir: string | null = null + private filePath: string | null = null + private fileStream: WriteStream | null = null + private ensureFilePromise: Promise | null = null + + // Readers + cleanup + private activeReaders = 0 + private cleanupPending = false + private cleanupRunning: Promise | null = null + + // Bookkeeping + private totalBytes = 0 + private finishedFlag = false + private digestValue: string | null = null + + constructor(opts: HashSpillWritableOptions) { + super(opts?.writableOptions) + if (!(opts?.limitInMemoryBytes > 0)) throw new Error('limitBytes must be a positive number') + + this.limitBytes = opts.limitInMemoryBytes + this.alg = opts.alg ?? 'sha256' + this.tmpRoot = opts.tmpRoot ?? os.tmpdir() + + this.hash = createHash(this.alg) + + this.on('error', () => { + void this.cleanupAsync() + }) + this.on('close', () => { + if (this.fileStream && !this.fileStream.closed) { + try { + this.fileStream.destroy() + } catch {} + } + }) + } + + // Writable implementation + _write(chunk: Buffer, _enc: BufferEncoding, cb: (error?: Error | null) => void): void { + try { + this.hash.update(chunk) + this.totalBytes += chunk.length + + if (!this.spilled) { + if (this.memSize + chunk.length <= this.limitBytes) { + this.chunks!.push(chunk) + this.memSize += chunk.length + cb() + return + } + // Spill + this.spilled = true + this.spillToDiskAndWrite(chunk).then(() => cb(), cb) + } else { + this.writeToFile(chunk).then(() => cb(), cb) + } + } catch (err) { + cb(err as Error) + } + } + + _final(cb: (error?: Error | null) => void): void { + const finalize = async () => { + if (this.spilled && this.fileStream) { + await new Promise((resolve, reject) => { + this.fileStream!.end((err: Error) => (err ? reject(err) : resolve())) + }) + await finished(this.fileStream).catch(() => {}) + } + if (!this.finishedFlag) { + this.digestValue = this.hash.digest('hex') + this.finishedFlag = true + } + } + finalize().then(() => cb(), cb) + } + + // Public API + + digestHex(): string { + if (!this.finishedFlag || !this.digestValue) { + throw new Error('digestHex() called before stream finished') + } + return this.digestValue + } + + size(): number { + return this.totalBytes + } + + toReadable(opts: ToReadableOptions = {}): Readable { + const { autoCleanup = false } = opts + + if (this.spilled) { + if (!this.filePath) throw new Error('Internal error: spilled but no filePath') + + const rs = fs.createReadStream(this.filePath) + this.activeReaders++ + + const done = () => { + rs.removeListener('close', done) + rs.removeListener('end', done) + rs.removeListener('error', done) + + this.activeReaders = Math.max(0, this.activeReaders - 1) + + if (autoCleanup && this.activeReaders === 0) { + this.cleanupPending = true + void this.maybeCleanupSpill() + } + } + + rs.once('close', done) + rs.once('end', done) + rs.once('error', done) + + return rs + } + + // In-memory: nothing to clean up + const snapshot = this.chunks ?? [] + return Readable.from(snapshot) + } + + /** Explicit cleanup (deferred if readers are still active). */ + async cleanup(): Promise { + this.cleanupPending = true + await this.maybeCleanupSpill() + } + + // Internals + + private async ensureFile(): Promise { + if (this.ensureFilePromise) return this.ensureFilePromise + + this.ensureFilePromise = (async () => { + this.tmpDir = await fsp.mkdtemp(path.join(this.tmpRoot, 'hashspill-')) // unique directory + const name = `${Date.now()}-${randomUUID()}.bin` // unique filename + this.filePath = path.join(this.tmpDir, name) + this.fileStream = fs.createWriteStream(this.filePath, { flags: 'wx' }) // fail if exists + })() + + return this.ensureFilePromise + } + + private writeToFile(buf: Buffer): Promise { + return new Promise((resolve, reject) => { + void this.ensureFile() + .then(() => { + const ok = this.fileStream!.write(buf) + if (ok) { + resolve() + return + } + + this.fileStream!.once('drain', resolve) + }) + .catch((error) => { + reject(error) + }) + }) + } + + private async spillToDiskAndWrite(nextChunk: Buffer): Promise { + await this.ensureFile() + + const prefix = Buffer.concat(this.chunks!, this.memSize) + // Free memory + this.chunks = null + this.memSize = 0 + + await this.writeToFile(prefix) + await this.writeToFile(nextChunk) + } + + private async maybeCleanupSpill(): Promise { + if (this.cleanupRunning) return this.cleanupRunning + + this.cleanupRunning = (async () => { + try { + if (!this.spilled) return + if (!this.cleanupPending) return + if (this.activeReaders > 0) return + + // Ensure file stream is closed + try { + if (this.fileStream && !this.fileStream.destroyed) { + this.fileStream.destroy() + } + } catch {} + + // Remove file and directory (best-effort) + try { + if (this.filePath) await fsp.rm(this.filePath, { force: true }) + } catch {} + try { + if (this.tmpDir) await fsp.rm(this.tmpDir, { force: true, recursive: true }) + } catch {} + + // Null out for GC + this.filePath = null + this.tmpDir = null + this.fileStream = null + } finally { + this.cleanupRunning = null + } + })() + + return this.cleanupRunning + } + + private async cleanupAsync(): Promise { + this.cleanupPending = true + await this.maybeCleanupSpill() + } +} diff --git a/src/internal/streams/index.ts b/src/internal/streams/index.ts index 4fc27fe17..87b727d1e 100644 --- a/src/internal/streams/index.ts +++ b/src/internal/streams/index.ts @@ -1,3 +1,5 @@ -export * from './stream-speed' export * from './byte-counter' +export * from './hash-stream' +export * from './min-chunk' export * from './monitor' +export * from './stream-speed' diff --git a/src/internal/streams/min-chunk.ts b/src/internal/streams/min-chunk.ts new file mode 100644 index 000000000..c3911a2eb --- /dev/null +++ b/src/internal/streams/min-chunk.ts @@ -0,0 +1,38 @@ +import { Transform, TransformCallback } from 'stream' + +/** + * A transform stream that buffers data until it has at least minChunkSize bytes + * before pushing downstream. This is useful for S3 Tables which requires + * chunks to be at least 8KB (except for the last chunk). + */ +export class MinChunkTransform extends Transform { + private buffer: Buffer = Buffer.alloc(0) + + constructor(private readonly minChunkSize: number) { + super() + } + + _transform(chunk: Buffer, encoding: BufferEncoding, callback: TransformCallback) { + try { + this.buffer = Buffer.concat([this.buffer, chunk]) + + // Push complete chunks of minChunkSize + while (this.buffer.length >= this.minChunkSize) { + this.push(this.buffer.subarray(0, this.minChunkSize)) + this.buffer = this.buffer.subarray(this.minChunkSize) + } + + callback() + } catch (err) { + callback(err as Error) + } + } + + _flush(callback: TransformCallback) { + // Push remaining data (last chunk can be smaller than minChunkSize) + if (this.buffer.length > 0) { + this.push(this.buffer) + } + callback() + } +} diff --git a/src/internal/streams/monitor.ts b/src/internal/streams/monitor.ts index 7db5dac60..afe3dbaab 100644 --- a/src/internal/streams/monitor.ts +++ b/src/internal/streams/monitor.ts @@ -1,7 +1,7 @@ +import { Readable } from 'node:stream' +import { trace } from '@opentelemetry/api' import { createByteCounterStream } from './byte-counter' import { monitorStreamSpeed } from './stream-speed' -import { trace } from '@opentelemetry/api' -import { Readable } from 'node:stream' /** * Monitor readable streams by tracking their speed and bytes read @@ -12,6 +12,8 @@ export function monitorStream(dataStream: Readable) { const byteCounter = createByteCounterStream() const span = trace.getActiveSpan() + // Limit measures array to prevent unbounded growth during long uploads + const MAX_MEASURES = 60 let measures: object[] = [] // Handle the 'speed' event to collect speed measurements @@ -25,6 +27,11 @@ export function monitorStream(dataStream: Readable) { }, }) + // Keep only the last MAX_MEASURES entries to bound memory usage + if (measures.length > MAX_MEASURES) { + measures = measures.slice(-MAX_MEASURES) + } + span?.setAttributes({ stream: JSON.stringify(measures), }) diff --git a/src/test/ndjson.test.ts b/src/internal/streams/ndjson.test.ts similarity index 99% rename from src/test/ndjson.test.ts rename to src/internal/streams/ndjson.test.ts index f788bfc7f..e1b04347d 100644 --- a/src/test/ndjson.test.ts +++ b/src/internal/streams/ndjson.test.ts @@ -1,7 +1,5 @@ -// NdJsonTransform.test.ts - -import { Buffer } from 'buffer' import { NdJsonTransform } from '@internal/streams/ndjson' +import { Buffer } from 'buffer' /** * Helper that writes the given chunks into the transform, diff --git a/src/internal/streams/ndjson.ts b/src/internal/streams/ndjson.ts index 2f48a8b07..bcbc84add 100644 --- a/src/internal/streams/ndjson.ts +++ b/src/internal/streams/ndjson.ts @@ -13,12 +13,12 @@ export class NdJsonTransform extends Transform { // decode safely across chunk boundaries this.buffer += this.decoder.write(chunk) - let newlineIdx: number - while ((newlineIdx = this.buffer.indexOf('\n')) !== -1) { + let newlineIdx = this.buffer.indexOf('\n') + while (newlineIdx !== -1) { const line = this.buffer.slice(0, newlineIdx) this.buffer = this.buffer.slice(newlineIdx + 1) if (line.trim()) { - let obj + let obj: unknown try { obj = JSON.parse(line) } catch (err) { @@ -32,6 +32,8 @@ export class NdJsonTransform extends Transform { // .push() participates in backpressure automatically this.push(obj) } + + newlineIdx = this.buffer.indexOf('\n') } callback() diff --git a/src/internal/streams/stream-speed.ts b/src/internal/streams/stream-speed.ts index 375a5675e..0a81d8914 100644 --- a/src/internal/streams/stream-speed.ts +++ b/src/internal/streams/stream-speed.ts @@ -1,5 +1,5 @@ -import { Readable } from 'stream' import { PassThrough } from 'node:stream' +import { Readable } from 'stream' /** * Keep track of a stream's speed diff --git a/src/internal/streams/types.d.ts b/src/internal/streams/types.d.ts new file mode 100644 index 000000000..95f61a3d0 --- /dev/null +++ b/src/internal/streams/types.d.ts @@ -0,0 +1,3 @@ +declare module 'stream' { + export function compose(s1: A, s2: B): B & A +} diff --git a/src/internal/testing/seeder/index.ts b/src/internal/testing/seeder/index.ts index ef91c6d2b..c979abf27 100644 --- a/src/internal/testing/seeder/index.ts +++ b/src/internal/testing/seeder/index.ts @@ -1,3 +1,3 @@ +export * from './base-seeder' export * from './knex-persistence' export * from './persistence' -export * from './base-seeder' diff --git a/src/internal/testing/seeder/knex-persistence.ts b/src/internal/testing/seeder/knex-persistence.ts index d82e589f2..f318abeb5 100644 --- a/src/internal/testing/seeder/knex-persistence.ts +++ b/src/internal/testing/seeder/knex-persistence.ts @@ -1,6 +1,7 @@ // src/persistence/KnexPersistence.ts -import { Persistence } from './persistence' + import knex, { Knex } from 'knex' +import { Persistence } from './persistence' export class KnexPersistence implements Persistence { private knex: Knex diff --git a/src/s3-app.ts b/src/s3-app.ts deleted file mode 100644 index 3d1fa8855..000000000 --- a/src/s3-app.ts +++ /dev/null @@ -1,37 +0,0 @@ -import fastify, { FastifyInstance, FastifyServerOptions } from 'fastify' -import { routes, schemas, plugins, setErrorHandler } from './http' -import { getConfig } from './config' - -const { keepAliveTimeout, headersTimeout } = getConfig() - -const buildS3 = (opts: FastifyServerOptions = {}): FastifyInstance => { - const app = fastify(opts) - - app.addContentTypeParser('*', function (request, payload, done) { - done(null) - }) - - app.server.keepAliveTimeout = keepAliveTimeout * 1000 - app.server.headersTimeout = headersTimeout * 1000 - - // Add common schemas - app.addSchema(schemas.authSchema) - app.addSchema(schemas.errorSchema) - - // Register only essential plugins for S3 - app.register(plugins.signals) - app.register(plugins.tenantId) - app.register(plugins.tracing) - app.register(plugins.logRequest({ excludeUrls: ['/status'] })) - - // Register S3 routes WITHOUT prefix - app.register(routes.s3) - - setErrorHandler(app) - - app.get('/status', async (request, response) => response.status(200).send()) - - return app -} - -export default buildS3 diff --git a/src/scripts/export-docs.ts b/src/scripts/export-docs.ts index 1b29b3f18..4dc55b6c7 100644 --- a/src/scripts/export-docs.ts +++ b/src/scripts/export-docs.ts @@ -1,6 +1,8 @@ import { promises as fs } from 'fs' +import buildAdmin from '../admin-app' import app from '../app' ;(async () => { + // Export main API spec const storageApp = app({ exposeDocs: true, }) @@ -13,4 +15,18 @@ import app from '../app' await fs.writeFile('static/api.json', response.body) await storageApp.close() + + // Export admin API spec + const adminApp = buildAdmin({ + exposeDocs: true, + }) + + const adminResponse = await adminApp.inject({ + method: 'GET', + url: '/documentation/json', + }) + + await fs.writeFile('static/api-admin.json', adminResponse.body) + + await adminApp.close() })().catch(console.error) diff --git a/src/scripts/migrate-call.ts b/src/scripts/migrate-call.ts index c1462ee56..4f5e7d9ef 100644 --- a/src/scripts/migrate-call.ts +++ b/src/scripts/migrate-call.ts @@ -1,12 +1,14 @@ import dotenv from 'dotenv' + dotenv.config() import { runMigrationsOnTenant } from '@internal/database/migrations' import { getConfig } from '../config' -;(async () => { + +void (async () => { const { databaseURL, dbMigrationFreezeAt } = getConfig() await runMigrationsOnTenant({ databaseUrl: databaseURL, upToMigration: dbMigrationFreezeAt, }) -})().catch(console.error) +})() diff --git a/src/scripts/migrations-types.ts b/src/scripts/migrations-types.ts index 708e36a68..24fdcf9f9 100644 --- a/src/scripts/migrations-types.ts +++ b/src/scripts/migrations-types.ts @@ -1,33 +1,46 @@ -import * as glob from 'glob' import fs from 'fs' import path from 'path' +const isIdentifier = (s: string) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s) + function main() { - const migrationsPath = path.join(__dirname, '..', '..', 'migrations', 'tenant', '*.sql') - const files = glob.sync(migrationsPath).sort() - - const migrations = files.map((file, index) => { - const fileName = file - .split(path.sep) - .pop() - ?.replace(/[0-9]+-/, '') - .replace('.sql', '') - - return { - file: fileName, + const migrationsDir = path.join(__dirname, '..', '..', 'migrations', 'tenant') + const files = fs + .readdirSync(migrationsDir, { withFileTypes: true }) + .filter((entry) => entry.isFile() && entry.name.endsWith('.sql')) + .map((entry) => entry.name) + .sort((a, b) => { + const numA = parseInt(path.basename(a).match(/^(\d+)/)?.[1] || '0', 10) + const numB = parseInt(path.basename(b).match(/^(\d+)/)?.[1] || '0', 10) + return numA - numB + }) + + const migrations = [ + // this migration is hardcoded by the postgres migrations library + { + file: 'create-migrations-table', + index: 0, + }, + ] + + files.forEach((file, index) => { + const fileName = file.replace(/[0-9]+-/, '').replace('.sql', '') + + migrations.push({ + file: fileName || '', index: index + 1, - } + }) }) const migrationsEnum = migrations.map((migration) => { - return ` '${migration.file}': ${migration.index},` + const key = isIdentifier(migration.file) ? migration.file : `'${migration.file}'` + return ` ${key}: ${migration.index},` }) - const template = ` - export const DBMigration = { - ${migrationsEnum.join('\n')} - } - ` + const template = `export const DBMigration = { +${migrationsEnum.join('\n')} +} as const +` const destinationPath = path.resolve( __dirname, diff --git a/src/scripts/orphan-client-stream.test.ts b/src/scripts/orphan-client-stream.test.ts new file mode 100644 index 000000000..f4039a769 --- /dev/null +++ b/src/scripts/orphan-client-stream.test.ts @@ -0,0 +1,156 @@ +import fs from 'fs/promises' +import os from 'os' +import path from 'path' +import { Readable } from 'stream' +import { vi } from 'vitest' +import { writeStreamToJsonArray } from './orphan-client-stream' + +describe('writeStreamToJsonArray', () => { + let tempDir: string + + beforeEach(async () => { + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'orphan-client-stream-')) + vi.spyOn(console, 'error').mockImplementation(() => {}) + vi.spyOn(console, 'log').mockImplementation(() => {}) + vi.spyOn(console, 'warn').mockImplementation(() => {}) + }) + + afterEach(async () => { + vi.restoreAllMocks() + await fs.rm(tempDir, { recursive: true, force: true }) + }) + + it('rejects when the server streams an error event', async () => { + const filePath = path.join(tempDir, 'orphan-objects.json') + const stream = Readable.from( + [ + { + event: 'data', + type: 'dbOrphans', + value: [{ name: 'my-object', version: 'v1', size: 1 }], + }, + { + event: 'error', + error: { + statusCode: '500', + code: 'InternalError', + error: 'InternalError', + message: 'Entity expansion limit exceeded', + }, + }, + ], + { objectMode: true } + ) + + await expect(writeStreamToJsonArray(stream, filePath)).rejects.toThrow( + '[InternalError] Entity expansion limit exceeded' + ) + await expect(fs.readFile(filePath, 'utf8')).resolves.toContain('"my-object"') + }) + + it('creates parent directories for the output file', async () => { + const filePath = path.join(tempDir, 'nested', 'orphan-objects.json') + const stream = Readable.from( + [ + { + event: 'data', + type: 's3Orphans', + value: [{ name: 'my-object', version: 'v1', size: 1 }], + }, + ], + { objectMode: true } + ) + + await expect(writeStreamToJsonArray(stream, filePath)).resolves.toBeUndefined() + await expect(fs.readFile(filePath, 'utf8')).resolves.toContain('"my-object"') + }) + + it('resolves after writing partial data when the delete limit is reached', async () => { + const filePath = path.join(tempDir, 'delete-limit.json') + const stream = Readable.from( + (async function* () { + yield { + event: 'data' as const, + type: 's3Orphans' as const, + value: [{ name: 'my-object', version: 'v1', size: 1 }], + } + + throw new Error('DELETE_LIMIT_REACHED') + })(), + { objectMode: true } + ) + + await expect(writeStreamToJsonArray(stream, filePath)).resolves.toBeUndefined() + await expect(fs.readFile(filePath, 'utf8')).resolves.toContain('"my-object"') + }) + + it('rejects after closing the JSON array on input stream errors', async () => { + const filePath = path.join(tempDir, 'stream-error.json') + const stream = Readable.from( + (async function* () { + yield { + event: 'data' as const, + type: 's3Orphans' as const, + value: [{ name: 'my-object', version: 'v1', size: 1 }], + } + + throw new Error('upstream stream failed') + })(), + { objectMode: true } + ) + + await expect(writeStreamToJsonArray(stream, filePath)).rejects.toThrow('upstream stream failed') + + const content = await fs.readFile(filePath, 'utf8') + + expect(JSON.parse(content)).toEqual([ + { + name: 'my-object', + version: 'v1', + size: 1, + orphanType: 's3Orphans', + }, + ]) + }) + + it('normalizes non-Error input stream failures', async () => { + const filePath = path.join(tempDir, 'non-error-stream-failure.json') + const stream = new Readable({ + objectMode: true, + read() { + this.destroy('stream failed' as unknown as Error) + }, + }) + + await expect(writeStreamToJsonArray(stream, filePath)).rejects.toThrow( + 'Unexpected stream failure' + ) + }) + + it('ignores malformed events that include a value field but are not data events', async () => { + const filePath = path.join(tempDir, 'invalid-event.json') + const stream = Readable.from( + [ + { + event: 'unexpected', + type: 's3Orphans', + value: [{ name: 'my-object', version: 'v1', size: 1 }], + }, + ] as Iterable, + { objectMode: true } + ) + + await expect(writeStreamToJsonArray(stream, filePath)).resolves.toBeUndefined() + + const content = await fs.readFile(filePath, 'utf8') + + expect(JSON.parse(content)).toEqual([]) + expect(console.warn).toHaveBeenCalled() + }) + + it('rejects when the local output stream cannot be opened', async () => { + const stream = Readable.from([], { objectMode: true }) + + await expect(writeStreamToJsonArray(stream, tempDir)).rejects.toThrow() + }) +}) diff --git a/src/scripts/orphan-client-stream.ts b/src/scripts/orphan-client-stream.ts new file mode 100644 index 000000000..301a75daf --- /dev/null +++ b/src/scripts/orphan-client-stream.ts @@ -0,0 +1,122 @@ +import fs from 'fs' +import { mkdir } from 'fs/promises' +import path from 'path' +import { pipeline } from 'stream/promises' + +export interface OrphanObject { + event: 'data' + type: 'dbOrphans' | 's3Orphans' + value: { + name: string + version: string + size: number + }[] +} + +export interface PingObject { + event: 'ping' +} + +export interface StreamedErrorPayload { + statusCode: string + code: string + error: string + message: string +} + +export interface ErrorObject { + event: 'error' + error: StreamedErrorPayload +} + +export type OrphanStreamEvent = OrphanObject | PingObject | ErrorObject + +export function formatOrphanStreamError(error: StreamedErrorPayload) { + return `[${error.code}] ${error.message}` +} + +export async function writeStreamToJsonArray( + stream: NodeJS.ReadableStream, + filePath: string +): Promise { + await mkdir(path.dirname(filePath), { recursive: true }) + + const localFile = fs.createWriteStream(filePath) + let isFirstItem = true + let receivedAnyData = false + let streamedError: Error | undefined + let deleteLimitReached = false + let inputStreamError: Error | undefined + + const jsonArrayStream = (async function* () { + yield '[\n' + + try { + for await (const data of stream as AsyncIterable) { + if (data.event === 'ping') { + console.log('Received ping event, ignoring') + continue + } + + if (data.event === 'error') { + streamedError = new Error(formatOrphanStreamError(data.error)) + console.error('Server error:', formatOrphanStreamError(data.error)) + continue + } + + if (data.event === 'data' && Array.isArray(data.value)) { + receivedAnyData = true + console.log(`Processing ${data.value.length} objects`) + + for (const item of data.value) { + if (!isFirstItem) { + yield ',\n' + } else { + isFirstItem = false + } + + yield JSON.stringify({ ...item, orphanType: data.type }, null, 2) + } + continue + } + + console.warn( + 'Received data with invalid format:', + JSON.stringify(data).substring(0, 100) + '...' + ) + } + } catch (err) { + if (err instanceof Error && err.message === 'DELETE_LIMIT_REACHED') { + deleteLimitReached = true + } else { + inputStreamError = + err instanceof Error ? err : new Error('Unexpected stream failure', { cause: err }) + console.error('Stream error:', inputStreamError) + } + } + + yield '\n]' + })() + + await pipeline(jsonArrayStream, localFile) + + if (inputStreamError) { + throw inputStreamError + } + + if (streamedError) { + throw streamedError + } + + if (!receivedAnyData) { + console.warn(`No data was received! File might be empty: ${filePath}`) + return + } + + if (deleteLimitReached) { + console.log(`Finished writing data to ${filePath}. Delete limit reached, data saved.`) + return + } + + console.log(`Finished writing data to ${filePath}. Data was received and saved.`) +} diff --git a/src/scripts/orphan-client.test.ts b/src/scripts/orphan-client.test.ts new file mode 100644 index 000000000..cf96eac79 --- /dev/null +++ b/src/scripts/orphan-client.test.ts @@ -0,0 +1,343 @@ +import { once } from 'events' +import fs from 'fs/promises' +import os from 'os' +import path from 'path' +import { Readable } from 'stream' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { + fetchOrphanStream, + main, + parseConfig, + resolveAdminUrl, + writeDeleteOrphanStream, + writeListOrphanStream, +} from './orphan-client' + +class PendingNdjsonStream extends Readable { + private sent = false + + _read() { + if (this.sent) { + return + } + + this.sent = true + this.push( + '{"event":"data","type":"s3Orphans","value":[{"name":"my-object","version":"v1","size":1}]}\n' + ) + } + + _destroy(_error: Error | null, callback: (error?: Error | null) => void) { + callback(null) + } +} + +describe('resolveAdminUrl', () => { + it('preserves base URL path prefixes for leading-slash request paths', () => { + const url = resolveAdminUrl( + 'http://localhost:54321/admin/', + '/tenants/test/buckets/public/orphan-objects' + ) + + expect(url.toString()).toBe( + 'http://localhost:54321/admin/tenants/test/buckets/public/orphan-objects' + ) + }) + + it('adds query parameters when provided', () => { + const url = resolveAdminUrl( + 'http://localhost:54321/admin/', + '/tenants/test/buckets/public/orphan-objects', + { + before: '2026-04-17T12:00:00.000Z', + } + ) + + expect(url.toString()).toBe( + 'http://localhost:54321/admin/tenants/test/buckets/public/orphan-objects?before=2026-04-17T12%3A00%3A00.000Z' + ) + }) +}) + +describe('parseConfig', () => { + it('uses the default delete limit when DELETE_LIMIT is omitted', () => { + expect( + parseConfig({ + ADMIN_URL: 'http://localhost:54321/admin', + ADMIN_API_KEY: 'test-key', + TENANT_ID: 'tenant-id', + BUCKET_ID: 'public', + }) + ).toEqual({ + adminUrl: 'http://localhost:54321/admin', + adminApiKey: 'test-key', + tenantId: 'tenant-id', + bucketId: 'public', + deleteLimit: 1000000, + before: undefined, + }) + }) + + it('reads ORPHAN_BEFORE into the request config', () => { + expect( + parseConfig({ + ADMIN_URL: 'http://localhost:54321/admin', + ADMIN_API_KEY: 'test-key', + TENANT_ID: 'tenant-id', + BUCKET_ID: 'public', + ORPHAN_BEFORE: '2026-04-17T12:00:00.000Z', + }) + ).toEqual({ + adminUrl: 'http://localhost:54321/admin', + adminApiKey: 'test-key', + tenantId: 'tenant-id', + bucketId: 'public', + deleteLimit: 1000000, + before: '2026-04-17T12:00:00.000Z', + }) + }) + + it.each([ + 'not-a-number', + '1x', + '0', + '-1', + ])('rejects invalid DELETE_LIMIT input %s', (deleteLimit) => { + expect( + parseConfig({ + ADMIN_URL: 'http://localhost:54321/admin', + ADMIN_API_KEY: 'test-key', + TENANT_ID: 'tenant-id', + BUCKET_ID: 'public', + DELETE_LIMIT: deleteLimit, + }) + ).toBe('Please provide a valid positive integer for DELETE_LIMIT') + }) +}) + +describe('main', () => { + afterEach(() => { + vi.restoreAllMocks() + process.exitCode = undefined + }) + + it('sets a non-zero exit code for invalid actions', async () => { + vi.spyOn(console, 'error').mockImplementation(() => {}) + + await expect( + main( + { + ADMIN_URL: 'http://localhost:54321/admin', + ADMIN_API_KEY: 'test-key', + TENANT_ID: 'tenant-id', + BUCKET_ID: 'public', + }, + ['node', 'orphan-client.ts', 'invalid'] + ) + ).resolves.toBe(false) + + expect(process.exitCode).toBe(1) + expect(console.error).toHaveBeenCalledWith('Please provide an action: list or delete') + }) + + it('sets a non-zero exit code for invalid config', async () => { + vi.spyOn(console, 'error').mockImplementation(() => {}) + + await expect( + main( + { + ADMIN_URL: 'http://localhost:54321/admin', + ADMIN_API_KEY: 'test-key', + TENANT_ID: 'tenant-id', + }, + ['node', 'orphan-client.ts', 'list'] + ) + ).resolves.toBe(false) + + expect(process.exitCode).toBe(1) + expect(console.error).toHaveBeenCalledWith('Please provide a bucket ID') + }) +}) + +describe('fetchOrphanStream', () => { + afterEach(() => { + vi.restoreAllMocks() + }) + + it('builds list requests with the before query and ApiKey header', async () => { + const fetchMock = vi + .spyOn(globalThis, 'fetch') + .mockResolvedValue(new Response('{"event":"ping"}\n')) + + const { stream } = await fetchOrphanStream({ + action: 'list', + adminApiKey: 'test-key', + adminUrl: 'http://localhost:54321/admin', + before: '2026-04-17T12:00:00.000Z', + bucketId: 'public', + tenantId: 'tenant-id', + }) + + stream.resume() + await once(stream, 'end') + + expect(fetchMock).toHaveBeenCalledTimes(1) + + const [requestUrl, requestInit] = fetchMock.mock.calls[0] + + expect(String(requestUrl)).toBe( + 'http://localhost:54321/admin/tenants/tenant-id/buckets/public/orphan-objects?before=2026-04-17T12%3A00%3A00.000Z' + ) + expect(requestInit?.method).toBe('GET') + expect(requestInit?.body).toBeUndefined() + expect((requestInit?.headers as Headers).get('ApiKey')).toBe('test-key') + expect((requestInit?.headers as Headers).get('Content-Type')).toBeNull() + expect(requestInit?.signal).toBeInstanceOf(AbortSignal) + }) + + it('builds delete requests with a JSON body', async () => { + const fetchMock = vi + .spyOn(globalThis, 'fetch') + .mockResolvedValue(new Response('{"event":"ping"}\n')) + + const { stream } = await fetchOrphanStream({ + action: 'delete', + adminApiKey: 'test-key', + adminUrl: 'http://localhost:54321/admin', + bucketId: 'public', + tenantId: 'tenant-id', + }) + + stream.resume() + await once(stream, 'end') + + const [, requestInit] = fetchMock.mock.calls[0] + + expect(requestInit?.method).toBe('DELETE') + expect(JSON.parse(requestInit?.body as string)).toEqual({ deleteS3Keys: true }) + expect((requestInit?.headers as Headers).get('ApiKey')).toBe('test-key') + expect((requestInit?.headers as Headers).get('Content-Type')).toBe('application/json') + }) + + it('throws the HTTP status and body on failed responses', async () => { + vi.spyOn(globalThis, 'fetch').mockResolvedValue( + new Response('permission denied', { + status: 403, + statusText: 'Forbidden', + }) + ) + + await expect( + fetchOrphanStream({ + action: 'list', + adminUrl: 'http://localhost:54321/admin', + bucketId: 'public', + tenantId: 'tenant-id', + }) + ).rejects.toThrow( + 'LIST http://localhost:54321/admin/tenants/tenant-id/buckets/public/orphan-objects failed with 403 Forbidden: permission denied' + ) + }) +}) + +describe('writeListOrphanStream', () => { + afterEach(() => { + vi.restoreAllMocks() + }) + + it('calls cancel after a successful write', async () => { + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'orphan-client-list-success-')) + const filePath = path.join(tempDir, 'list.json') + const requestStream = Readable.from( + [ + '{"event":"data","type":"s3Orphans","value":[{"name":"my-object","version":"v1","size":1}]}\n', + ], + { objectMode: false } + ) + const cancel = vi.fn() + + vi.spyOn(console, 'log').mockImplementation(() => {}) + + try { + await expect( + writeListOrphanStream({ + requestStream, + cancel, + filePath, + }) + ).resolves.toBeUndefined() + + expect(cancel).toHaveBeenCalledTimes(1) + await expect(fs.readFile(filePath, 'utf8')).resolves.toContain('"my-object"') + } finally { + await fs.rm(tempDir, { recursive: true, force: true }) + } + }) + + it('calls cancel when the local output write fails', async () => { + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'orphan-client-list-failure-')) + const requestStream = Readable.from( + [ + '{"event":"data","type":"s3Orphans","value":[{"name":"my-object","version":"v1","size":1}]}\n', + ], + { objectMode: false } + ) + const cancel = vi.fn() + + vi.spyOn(console, 'log').mockImplementation(() => {}) + + try { + await expect( + writeListOrphanStream({ + requestStream, + cancel, + filePath: tempDir, + }) + ).rejects.toThrow() + + expect(cancel).toHaveBeenCalledTimes(1) + } finally { + await fs.rm(tempDir, { recursive: true, force: true }) + } + }) +}) + +describe('writeDeleteOrphanStream', () => { + afterEach(() => { + vi.restoreAllMocks() + }) + + it('suppresses request-stream teardown errors after the delete limit cancels the stream', async () => { + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'orphan-client-delete-limit-')) + const filePath = path.join(tempDir, 'delete-limit.json') + const destroyError = Object.assign(new Error('stream closed early'), { + code: 'ERR_STREAM_PREMATURE_CLOSE', + name: 'PrematureCloseError', + }) + const requestStream = new PendingNdjsonStream() + const cancel = vi.fn(() => { + requestStream.destroy() + process.nextTick(() => { + requestStream.emit('error', destroyError) + }) + }) + + vi.spyOn(console, 'log').mockImplementation(() => {}) + + try { + await expect( + writeDeleteOrphanStream({ + requestStream, + cancel, + deleteLimit: 1, + filePath, + }) + ).resolves.toBeUndefined() + + expect(cancel).toHaveBeenCalledTimes(1) + await expect(fs.readFile(filePath, 'utf8')).resolves.toContain('"my-object"') + } finally { + await fs.rm(tempDir, { recursive: true, force: true }) + } + }) +}) diff --git a/src/scripts/orphan-client.ts b/src/scripts/orphan-client.ts index 98339b38b..a403a5e33 100644 --- a/src/scripts/orphan-client.ts +++ b/src/scripts/orphan-client.ts @@ -1,185 +1,335 @@ -import axios from 'axios' import { NdJsonTransform } from '@internal/streams/ndjson' -import fs from 'fs' import path from 'path' +import { Readable } from 'stream' +import type { ReadableStream as NodeReadableStream } from 'stream/web' +import { OrphanStreamEvent, writeStreamToJsonArray } from './orphan-client-stream' -const ADMIN_URL = process.env.ADMIN_URL -const ADMIN_API_KEY = process.env.ADMIN_API_KEY -const TENANT_ID = process.env.TENANT_ID -const BUCKET_ID = process.env.BUCKET_ID - -const BEFORE = undefined // new Date().toISOString() - -const FILE_PATH = (operation: string) => - `../../dist/${operation}-${TENANT_ID}-${Date.now()}-orphan-objects.json` - -const client = axios.create({ - baseURL: ADMIN_URL, - headers: { - ApiKey: ADMIN_API_KEY, - }, -}) - -interface OrphanObject { - event: 'data' - type: 's3Orphans' - value: { - name: string - version: string - size: number - }[] +const DEFAULT_DELETE_LIMIT = 1000000 + +type OrphanAction = 'list' | 'delete' + +interface OrphanClientConfig { + adminUrl: string + adminApiKey: string + tenantId: string + + // bucket id to search, can handle multiple comma delimited buckets (aaa,bbb,ccc) + bucketId: string + + // limits the number of delete operations to avoid overwhelming our queue + deleteLimit: number + + // optional cutoff override for orphan list/delete requests + before?: string } -interface PingObject { - event: 'ping' +interface FetchOrphanStreamOptions { + action: OrphanAction + adminApiKey?: string + adminUrl: string + before?: string + bucketId: string + tenantId: string } -async function main() { - const action = process.argv[2] +interface WriteListOrphanStreamOptions { + requestStream: Readable + cancel: () => void + filePath: string +} - if (!action) { - console.error('Please provide an action: list or delete') - return +interface WriteDeleteOrphanStreamOptions { + requestStream: Readable + cancel: () => void + deleteLimit: number + filePath: string +} + +const FILE_PATH = (operation: string, tenantId: string, bucketId: string) => + `../../dist/${operation}-${tenantId}-${bucketId}-${Date.now()}-orphan-objects.json` + +export function parseConfig(env: NodeJS.ProcessEnv): OrphanClientConfig | string { + const { ADMIN_URL, ADMIN_API_KEY, TENANT_ID, BUCKET_ID, DELETE_LIMIT, ORPHAN_BEFORE } = env + const rawDeleteLimit = DELETE_LIMIT?.trim() || String(DEFAULT_DELETE_LIMIT) + const deleteLimit = Number.parseInt(rawDeleteLimit, 10) + + if (!ADMIN_URL) return 'Please provide an admin URL' + if (!ADMIN_API_KEY) return 'Please provide an admin API key' + if (!TENANT_ID) return 'Please provide a tenant ID' + if (!BUCKET_ID) return 'Please provide a bucket ID' + if (!/^\d+$/.test(rawDeleteLimit) || !Number.isSafeInteger(deleteLimit) || deleteLimit <= 0) { + return 'Please provide a valid positive integer for DELETE_LIMIT' } - if (!TENANT_ID) { - console.error('Please provide a tenant ID') - return + return { + adminUrl: ADMIN_URL, + adminApiKey: ADMIN_API_KEY, + tenantId: TENANT_ID, + bucketId: BUCKET_ID, + deleteLimit, + before: ORPHAN_BEFORE, } +} + +export function resolveAdminUrl( + baseUrl: string, + requestPath: string, + query?: Record +): URL { + const url = new URL(`${baseUrl.replace(/\/+$/, '')}/${requestPath.replace(/^\/+/, '')}`) - if (!BUCKET_ID) { - console.error('Please provide a bucket ID') - return + for (const [key, value] of Object.entries(query ?? {})) { + if (value !== undefined) { + url.searchParams.set(key, value) + } } - if (action === 'list') { - await listOrphans(TENANT_ID, BUCKET_ID) - return + return url +} + +async function assertStreamResponse(response: Response, context: string) { + if (!response.ok) { + const body = await response.text() + const details = body ? `: ${body}` : '' + + throw new Error(`${context} failed with ${response.status} ${response.statusText}${details}`) } - await deleteS3Orphans(TENANT_ID, BUCKET_ID) + if (!response.body) { + throw new Error(`${context} returned an empty response body`) + } } -/** - * List Orphan objects in a bucket - * @param tenantId - * @param bucketId - */ -async function listOrphans(tenantId: string, bucketId: string) { - const request = await client.get(`/tenants/${tenantId}/buckets/${bucketId}/orphan-objects`, { - responseType: 'stream', - params: { - before: BEFORE, - }, +export async function fetchOrphanStream(options: FetchOrphanStreamOptions) { + const requestPath = `/tenants/${options.tenantId}/buckets/${options.bucketId}/orphan-objects` + const url = resolveAdminUrl( + options.adminUrl, + requestPath, + options.action === 'list' ? { before: options.before } : undefined + ) + const headers = new Headers() + + if (options.adminApiKey) { + headers.set('ApiKey', options.adminApiKey) + } + + const requestBody = + options.action === 'delete' + ? JSON.stringify({ + deleteS3Keys: true, + before: options.before, + }) + : undefined + + if (requestBody) { + headers.set('Content-Type', 'application/json') + } + + const controller = new AbortController() + const response = await fetch(url, { + method: options.action === 'list' ? 'GET' : 'DELETE', + headers, + body: requestBody, + signal: controller.signal, }) - const transformStream = new NdJsonTransform() - request.data.on('error', (err: Error) => { - transformStream.emit('error', err) + await assertStreamResponse(response, `${options.action.toUpperCase()} ${url}`) + + const stream = Readable.fromWeb(response.body as unknown as NodeReadableStream) + let completed = false + + stream.once('end', () => { + completed = true }) - const jsonStream = request.data.pipe(transformStream) + stream.once('close', () => { + if (!completed && !controller.signal.aborted) { + controller.abort() + } + }) - await writeStreamToJsonArray(jsonStream, FILE_PATH('list')) -} + return { + stream, + cancel: () => { + if (!stream.destroyed) { + stream.destroy() + } -/** - * Deletes S3 orphan objects in a bucket - * @param tenantId - * @param bucketId - */ -async function deleteS3Orphans(tenantId: string, bucketId: string) { - const request = await client.delete(`/tenants/${tenantId}/buckets/${bucketId}/orphan-objects`, { - responseType: 'stream', - data: { - deleteS3Keys: true, - before: BEFORE, + if (!controller.signal.aborted) { + controller.abort() + } }, - }) + } +} + +export async function writeListOrphanStream(options: WriteListOrphanStreamOptions) { + let cancelled = false + const cancelRequest = () => { + if (!cancelled) { + cancelled = true + options.cancel() + } + } const transformStream = new NdJsonTransform() - request.data.on('error', (err: Error) => { + options.requestStream.on('error', (err: Error) => { transformStream.emit('error', err) }) - const jsonStream = request.data.pipe(transformStream) + const jsonStream = options.requestStream.pipe(transformStream) - await writeStreamToJsonArray(jsonStream, FILE_PATH('delete')) + try { + await writeStreamToJsonArray(jsonStream, options.filePath) + } finally { + cancelRequest() + } } -/** - * Writes the output to a JSON array - * @param stream - * @param relativePath - */ -async function writeStreamToJsonArray( - stream: NodeJS.ReadableStream, - relativePath: string -): Promise { - const filePath = path.resolve(__dirname, relativePath) - const localFile = fs.createWriteStream(filePath) - - // Start with an empty array - localFile.write('[\n') - let isFirstItem = true - - return new Promise((resolve, reject) => { - let receivedAnyData = false - - stream.on('data', (data: OrphanObject | PingObject) => { - if (data.event === 'ping') { - console.log('Received ping event, ignoring') - return - } +export async function writeDeleteOrphanStream(options: WriteDeleteOrphanStreamOptions) { + let cancelled = false + const cancelRequest = () => { + if (!cancelled) { + cancelled = true + options.cancel() + } + } + + const transformStream = new NdJsonTransform() + let deleteLimitReached = false - if (data.event === 'data' && data.value && Array.isArray(data.value)) { - receivedAnyData = true - console.log(`Processing ${data.value.length} objects`) + options.requestStream.on('error', (err: Error) => { + if (deleteLimitReached) { + return + } - for (const item of data.value) { - if (!isFirstItem) { - localFile.write(',\n') - } else { - isFirstItem = false - } + transformStream.emit('error', err) + }) + + const jsonStream = options.requestStream.pipe(transformStream) + + let itemCount = 0 + const limitedStream = Readable.from( + (async function* () { + for await (const chunk of jsonStream as AsyncIterable) { + yield chunk - localFile.write(JSON.stringify(item, null, 2)) + if (chunk.event === 'data' && chunk.value && Array.isArray(chunk.value)) { + itemCount += chunk.value.length + + if (itemCount >= options.deleteLimit) { + deleteLimitReached = true + console.log( + `Delete limit of ${options.deleteLimit} reached. Stopping after this batch. Ensure these operations complete before queuing additional jobs.` + ) + cancelRequest() + return + } } - } else { - console.warn( - 'Received data with invalid format:', - JSON.stringify(data).substring(0, 100) + '...' - ) } - }) + })(), + { objectMode: true } + ) - stream.on('error', (err) => { - console.error('Stream error:', err) - localFile.end('\n]', () => { - reject(err) - }) - }) + try { + await writeStreamToJsonArray(limitedStream, options.filePath) + } finally { + cancelRequest() + } +} - stream.on('end', () => { - localFile.write('\n]') - localFile.end(() => { - resolve() - }) - - if (!receivedAnyData) { - console.warn(`No data was received! File might be empty: ${filePath}`) - } else { - // Check if the file exists and has content - console.log(`Finished writing data to ${filePath}. Data was received and saved.`) - } - }) +function failCli(message: string) { + process.exitCode = 1 + console.error(message) + return false +} + +export async function main( + env: NodeJS.ProcessEnv = process.env, + argv: string[] = process.argv +): Promise { + const action = argv[2] + + if (action !== 'list' && action !== 'delete') { + return failCli('Please provide an action: list or delete') + } + + const config = parseConfig(env) + if (typeof config === 'string') { + return failCli(config) + } + + const buckets = config.bucketId + .split(',') + .map((bucketId) => bucketId.trim()) + .filter(Boolean) + + for (const bucket of buckets) { + console.log(' ') + console.log(`${action} items in bucket ${bucket}...`) + if (action === 'list') { + await listOrphans(config, bucket) + } else { + await deleteS3Orphans(config, bucket) + } + } + + return true +} + +/** + * List Orphan objects in a bucket + * @param tenantId + * @param bucketId + */ +async function listOrphans(config: OrphanClientConfig, bucketId: string) { + const { stream: requestStream, cancel } = await fetchOrphanStream({ + action: 'list', + adminApiKey: config.adminApiKey, + adminUrl: config.adminUrl, + before: config.before, + bucketId, + tenantId: config.tenantId, + }) + + await writeListOrphanStream({ + requestStream, + cancel, + filePath: path.resolve(__dirname, FILE_PATH('list', config.tenantId, bucketId)), }) } -main() - .then(() => { - console.log('Done') +/** + * Deletes S3 orphan objects in a bucket + * @param tenantId + * @param bucketId + */ +async function deleteS3Orphans(config: OrphanClientConfig, bucketId: string) { + const { stream: requestStream, cancel } = await fetchOrphanStream({ + action: 'delete', + adminApiKey: config.adminApiKey, + adminUrl: config.adminUrl, + before: config.before, + bucketId, + tenantId: config.tenantId, }) - .catch((e) => { - console.error('Error:', e) + + await writeDeleteOrphanStream({ + requestStream, + cancel, + deleteLimit: config.deleteLimit, + filePath: path.resolve(__dirname, FILE_PATH('delete', config.tenantId, bucketId)), }) +} + +if (typeof require !== 'undefined' && typeof module !== 'undefined' && require.main === module) { + void main() + .then((ok) => { + if (ok) { + console.log('Done') + } + }) + .catch((e) => { + process.exitCode = 1 + console.error('Error:', e) + }) +} diff --git a/src/scripts/pprof-client.test.ts b/src/scripts/pprof-client.test.ts new file mode 100644 index 000000000..4c4a12665 --- /dev/null +++ b/src/scripts/pprof-client.test.ts @@ -0,0 +1,80 @@ +import { + parseBooleanEnv, + parseBooleanEnvWithDefault, + parseNonNegativeIntegerEnv, + parsePositiveIntegerEnv, + parsePprofTarget, +} from './pprof-client' + +describe('parsePprofTarget', () => { + it('uses the default seconds when only the type is provided', () => { + expect(parsePprofTarget('profile', 60)).toEqual({ + seconds: 60, + type: 'profile', + }) + expect(parsePprofTarget('heap', 30)).toEqual({ + seconds: 30, + type: 'heap', + }) + }) + + it('parses inline seconds overrides', () => { + expect(parsePprofTarget('profile:10', 60)).toEqual({ + seconds: 10, + type: 'profile', + }) + expect(parsePprofTarget('heap:5', 60)).toEqual({ + seconds: 5, + type: 'heap', + }) + }) + + it('rejects invalid targets', () => { + expect(() => parsePprofTarget(undefined, 60)).toThrow('Usage:') + expect(() => parsePprofTarget('cpu', 60)).toThrow('Usage:') + expect(() => parsePprofTarget('profile:abc', 60)).toThrow('seconds must be a positive integer') + expect(() => parsePprofTarget('heap:0', 60)).toThrow('seconds must be a positive integer') + expect(() => parsePprofTarget('profile:10abc', 60)).toThrow( + 'seconds must be a positive integer' + ) + expect(() => parsePprofTarget('profile:10:extra', 60)).toThrow('Usage:') + }) +}) + +describe('pprof client env parsing', () => { + it('parses boolean env values', () => { + expect(parseBooleanEnv(undefined)).toBeUndefined() + expect(parseBooleanEnv('true')).toBe(true) + expect(parseBooleanEnv('OFF')).toBe(false) + expect(() => parseBooleanEnv('maybe')).toThrow('Invalid boolean value') + }) + + it('parses boolean env values with defaults', () => { + expect(parseBooleanEnvWithDefault(undefined, true)).toBe(true) + expect(parseBooleanEnvWithDefault(undefined, false)).toBe(false) + expect(parseBooleanEnvWithDefault('no', true)).toBe(false) + }) + + it('parses positive integer env values strictly', () => { + expect(parsePositiveIntegerEnv(undefined, 'PPROF_SECONDS', 60)).toBe(60) + expect(parsePositiveIntegerEnv('90', 'PPROF_SECONDS', 60)).toBe(90) + expect(() => parsePositiveIntegerEnv('0', 'PPROF_SECONDS', 60)).toThrow( + 'PPROF_SECONDS must be a positive integer' + ) + expect(() => parsePositiveIntegerEnv('12ms', 'PPROF_SECONDS', 60)).toThrow( + 'PPROF_SECONDS must be a positive integer' + ) + }) + + it('parses non-negative integer env values strictly', () => { + expect(parseNonNegativeIntegerEnv(undefined, 'PPROF_WORKER_ID')).toBeUndefined() + expect(parseNonNegativeIntegerEnv('0', 'PPROF_WORKER_ID')).toBe(0) + expect(parseNonNegativeIntegerEnv('7', 'PPROF_WORKER_ID')).toBe(7) + expect(() => parseNonNegativeIntegerEnv('-1', 'PPROF_WORKER_ID')).toThrow( + 'PPROF_WORKER_ID must be a non-negative integer' + ) + expect(() => parseNonNegativeIntegerEnv('7x', 'PPROF_WORKER_ID')).toThrow( + 'PPROF_WORKER_ID must be a non-negative integer' + ) + }) +}) diff --git a/src/scripts/pprof-client.ts b/src/scripts/pprof-client.ts new file mode 100644 index 000000000..edb3acbe7 --- /dev/null +++ b/src/scripts/pprof-client.ts @@ -0,0 +1,200 @@ +import { fetchPprofStream } from '@internal/monitoring/pprof/client-http' +import { writeMultipartPprofToFile } from '@internal/monitoring/pprof/download' +import { generateFlameArtifacts, resolveFlameMdFormat } from '@internal/monitoring/pprof/flame' +import type { PprofRequestTargetType } from '@internal/monitoring/pprof/types' +import path from 'path' + +const ADMIN_URL = process.env.ADMIN_URL +const ADMIN_API_KEY = process.env.ADMIN_API_KEY +const PPROF_SECONDS = process.env.PPROF_SECONDS +const PPROF_GENERATE_FLAME = process.env.PPROF_GENERATE_FLAME +const PPROF_FLAME_MD_FORMAT = process.env.PPROF_FLAME_MD_FORMAT +const PPROF_WORKER_ID = process.env.PPROF_WORKER_ID +const PPROF_SOURCE_MAPS = process.env.PPROF_SOURCE_MAPS +const PPROF_NODE_MODULES_SOURCE_MAPS = process.env.PPROF_NODE_MODULES_SOURCE_MAPS +const PPROF_OUTPUT = process.env.PPROF_OUTPUT + +export function parseBooleanEnv(value: string | undefined) { + if (value === undefined) { + return undefined + } + + const normalized = value.trim().toLowerCase() + if (['1', 'true', 'yes', 'on'].includes(normalized)) { + return true + } + + if (['0', 'false', 'no', 'off'].includes(normalized)) { + return false + } + + throw new Error(`Invalid boolean value: ${value}`) +} + +export function parseBooleanEnvWithDefault(value: string | undefined, defaultValue: boolean) { + if (value === undefined) { + return defaultValue + } + + return parseBooleanEnv(value) === true +} + +function parseUnsignedInteger(value: string, errorMessage: string) { + const normalized = value.trim() + + if (!/^\d+$/.test(normalized)) { + throw new Error(errorMessage) + } + + const parsed = Number.parseInt(normalized, 10) + if (!Number.isSafeInteger(parsed)) { + throw new Error(errorMessage) + } + + return parsed +} + +export function parsePositiveIntegerEnv( + value: string | undefined, + envName: string, + defaultValue: number +) { + if (value === undefined) { + return defaultValue + } + + const parsed = parseUnsignedInteger(value, `${envName} must be a positive integer`) + if (parsed <= 0) { + throw new Error(`${envName} must be a positive integer`) + } + + return parsed +} + +export function parseNonNegativeIntegerEnv(value: string | undefined, envName: string) { + if (value === undefined) { + return undefined + } + + return parseUnsignedInteger(value, `${envName} must be a non-negative integer`) +} + +export function parsePprofTarget( + value: string | undefined, + defaultSeconds: number +): { seconds: number; type: PprofRequestTargetType } { + if (!value) { + throw new Error( + 'Usage: tsx src/scripts/pprof-client.ts [output-file]' + ) + } + + const [type, secondsValue, ...rest] = value.split(':') + + if (rest.length > 0 || (type !== 'profile' && type !== 'heap')) { + throw new Error( + 'Usage: tsx src/scripts/pprof-client.ts [output-file]' + ) + } + + if (secondsValue === undefined || secondsValue === '') { + return { + seconds: defaultSeconds, + type, + } + } + + const seconds = parseUnsignedInteger(secondsValue, 'seconds must be a positive integer') + if (seconds <= 0) { + throw new Error('seconds must be a positive integer') + } + + return { + seconds, + type, + } +} + +function fail(message: string) { + process.exitCode = 1 + console.error(message) +} + +async function main() { + const target = process.argv[2] + const outputArg = process.argv[3] + let parsedTarget: ReturnType + let pprofSeconds: number + let sourceMaps: boolean | undefined + let workerId: number | undefined + let generateFlame: boolean + + if (!ADMIN_URL) { + fail('Please provide ADMIN_URL') + return + } + + if (!ADMIN_API_KEY) { + fail('Please provide ADMIN_API_KEY') + return + } + + try { + pprofSeconds = parsePositiveIntegerEnv(PPROF_SECONDS, 'PPROF_SECONDS', 60) + parsedTarget = parsePprofTarget(target, pprofSeconds) + sourceMaps = parseBooleanEnv(PPROF_SOURCE_MAPS) + workerId = parseNonNegativeIntegerEnv(PPROF_WORKER_ID, 'PPROF_WORKER_ID') + generateFlame = parseBooleanEnvWithDefault(PPROF_GENERATE_FLAME, true) + } catch (error) { + process.exitCode = 1 + console.error(error instanceof Error ? error.message : error) + return + } + + const flameMdFormat = resolveFlameMdFormat(PPROF_FLAME_MD_FORMAT) + + const response = await fetchPprofStream({ + adminUrl: ADMIN_URL, + apiKey: ADMIN_API_KEY, + nodeModulesSourceMaps: PPROF_NODE_MODULES_SOURCE_MAPS || undefined, + seconds: parsedTarget.seconds, + sourceMaps, + type: parsedTarget.type, + workerId, + }) + + const outputPath = outputArg + ? path.resolve(outputArg) + : PPROF_OUTPUT + ? path.resolve(PPROF_OUTPUT) + : undefined + + const { outputPath: capturedProfilePath } = await writeMultipartPprofToFile( + response.stream, + response.contentType, + { + outputPath, + } + ) + + if (!generateFlame) { + return + } + + await generateFlameArtifacts(capturedProfilePath, { + env: { + ...process.env, + FLAME_SOURCEMAPS_DIRS: process.env.FLAME_SOURCEMAPS_DIRS || 'dist', + }, + mdFormat: flameMdFormat, + }) +} + +// Keep the CLI side effect behind a CommonJS-friendly main-module gate so tests can import +// the helpers without starting a capture. +if (require.main === module) { + main().catch((error) => { + process.exitCode = 1 + console.error(error) + }) +} diff --git a/src/scripts/test-migration-idempotency.ts b/src/scripts/test-migration-idempotency.ts new file mode 100644 index 000000000..947e2c874 --- /dev/null +++ b/src/scripts/test-migration-idempotency.ts @@ -0,0 +1,40 @@ +import dotenv from 'dotenv' + +dotenv.config() + +import { resetMigration, runMigrationsOnTenant } from '@internal/database/migrations' +import { DBMigration } from '@internal/database/migrations/types' +import { getConfig } from '../config' + +void (async () => { + const { databaseURL, dbMigrationFreezeAt } = getConfig() + const migrations = Object.keys(DBMigration) as (keyof typeof DBMigration)[] + + let previousMigration: keyof typeof DBMigration = 'create-migrations-table' + + for (const migration of migrations.slice(1)) { + console.log(`Running migration ${migration}`) + await runMigrationsOnTenant({ + databaseUrl: databaseURL, + upToMigration: migration, + }) + + console.log(`Resetting migration ${migration}`) + await resetMigration({ + databaseUrl: databaseURL, + untilMigration: previousMigration, + }) + + console.log(`Rerunning migration ${migration}`) + await runMigrationsOnTenant({ + databaseUrl: databaseURL, + upToMigration: migration, + }) + + if (dbMigrationFreezeAt === migration) { + break + } + + previousMigration = migration + } +})() diff --git a/src/start/server.ts b/src/start/server.ts index 850fde69f..0e5c6e3ad 100644 --- a/src/start/server.ts +++ b/src/start/server.ts @@ -1,28 +1,36 @@ -import '@internal/monitoring/otel' -import { FastifyInstance } from 'fastify' -import { IncomingMessage, Server, ServerResponse } from 'node:http' +import '@internal/monitoring/otel-tracing' +import '@internal/monitoring/otel-metrics' -import build from '../app' -import buildAdmin from '../admin-app' -import { getConfig } from '../config' -import { listenForTenantUpdate, PubSub, TenantConnection } from '@internal/database' -import { logger, logSchema } from '@internal/monitoring' -import { Queue } from '@internal/queue' -import { registerWorkers } from '@storage/events' +import { IncomingMessage, Server, ServerResponse } from 'node:http' +import { Cluster } from '@internal/cluster/cluster' import { AsyncAbortController } from '@internal/concurrency' - -import { bindShutdownSignals, createServerClosedPromise, shutdown } from './shutdown' +import { + listenForTenantUpdate, + multitenantKnex, + PubSub, + TenantConnection, +} from '@internal/database' import { runMigrationsOnTenant, runMultitenantMigrations, startAsyncMigrations, } from '@internal/database/migrations' -import { Cluster } from '@internal/cluster/cluster' -import buildS3 from '../s3-app' +import { logger, logSchema } from '@internal/monitoring' +import { Queue } from '@internal/queue' +import { KnexShardStoreFactory, ShardCatalog } from '@internal/sharding' +import { getGlobal } from '@platformatic/globals' +import { registerWorkers } from '@storage/events' +import { SyncCatalogIds } from '@storage/events/upgrades/sync-catalog-ids' +import { FastifyInstance } from 'fastify' +import buildAdmin from '../admin-app' +import build from '../app' +import { getConfig } from '../config' +import { bindShutdownSignals, createServerClosedPromise, shutdown } from './shutdown' const shutdownSignal = new AsyncAbortController() bindShutdownSignals(shutdownSignal) +registerPlatformaticCloseHandler() // Start API server main() @@ -37,7 +45,7 @@ main() error: e, }) - await shutdown(shutdownSignal) + await close() process.exit(1) }) .catch(() => { @@ -48,24 +56,21 @@ main() * Start Storage API server */ async function main() { - const { databaseURL, isMultitenant, pgQueueEnable, dbMigrationFreezeAt } = getConfig() - - // Migrations - if (isMultitenant) { - await runMultitenantMigrations() - await listenForTenantUpdate(PubSub) - } else { - await runMigrationsOnTenant({ - databaseUrl: databaseURL, - upToMigration: dbMigrationFreezeAt, - }) - } + const { + databaseURL, + isMultitenant, + pgQueueEnable, + dbMigrationFreezeAt, + vectorS3Buckets, + icebergShards, + numWorkers, + } = getConfig() // Queue if (pgQueueEnable) { await Queue.start({ signal: shutdownSignal.nextGroup.signal, - registerWorkers: registerWorkers, + registerWorkers, }) logSchema.info(logger, '[Queue] Started', { @@ -73,6 +78,41 @@ async function main() { }) } + // Sharding for special buckets (vectors, analytics) + const sharding = new ShardCatalog(new KnexShardStoreFactory(multitenantKnex)) + + // Migrations + if (isMultitenant) { + await runMultitenantMigrations() + await upgrades() + await listenForTenantUpdate(PubSub) + + // Create shards for vector S3 buckets + await sharding.createShards( + vectorS3Buckets?.map((s) => ({ + shardKey: s, + kind: 'vector', + capacity: 10000, + status: 'active', + })) + ) + + // Create shards for analytics buckets + await sharding.createShards( + icebergShards.map((shard) => ({ + shardKey: shard, + kind: 'iceberg-table', + capacity: 10000, + status: 'active', + })) + ) + } else { + await runMigrationsOnTenant({ + databaseUrl: databaseURL, + upToMigration: dbMigrationFreezeAt, + }) + } + // Pubsub await PubSub.start({ signal: shutdownSignal.nextGroup.signal, @@ -83,17 +123,21 @@ async function main() { startAsyncMigrations(shutdownSignal.nextGroup.signal) } - // PoolManager Monitoring - TenantConnection.poolManager.monitor(shutdownSignal.nextGroup.signal) + // PoolManager + TenantConnection.poolManager.setNumWorkers(numWorkers) + TenantConnection.poolManager.monitor() // Cluster information await Cluster.init(shutdownSignal.nextGroup.signal) Cluster.on('change', (data) => { - logger.info(`[Cluster] Cluster size changed to ${data.size}`, { - type: 'cluster', - clusterSize: data.size, - }) + logger.info( + { + type: 'cluster', + clusterSize: data.size, + }, + `[Cluster] Cluster size changed to ${data.size}` + ) TenantConnection.poolManager.rebalanceAll({ clusterSize: data.size, }) @@ -101,7 +145,6 @@ async function main() { // HTTP Server const app = await httpServer(shutdownSignal.signal) - await httpS3Server(shutdownSignal.signal) // HTTP Admin Server if (isMultitenant) { @@ -121,10 +164,10 @@ async function httpServer(signal: AbortSignal) { disableRequestLogging: true, exposeDocs, requestIdHeader: requestTraceHeader, - maxParamLength: 2500, + routerOptions: { maxParamLength: 2500 }, }) - const closePromise = createServerClosedPromise(app.server, () => { + const serverClosedPromise = createServerClosedPromise(app.server, () => { logSchema.info(logger, '[Server] Exited', { type: 'server', }) @@ -138,7 +181,7 @@ async function httpServer(signal: AbortSignal) { type: 'server', }) - await closePromise + await serverClosedPromise }, { once: true } ) @@ -163,18 +206,16 @@ async function httpAdminServer( app: FastifyInstance, signal: AbortSignal ) { - const { adminRequestIdHeader, adminPort, host } = getConfig() + const { exposeDocs, adminRequestIdHeader, adminPort, host } = getConfig() - const adminApp = buildAdmin( - { - loggerInstance: logger, - disableRequestLogging: true, - requestIdHeader: adminRequestIdHeader, - }, - app - ) + const adminApp = buildAdmin({ + loggerInstance: logger, + disableRequestLogging: true, + exposeDocs, + requestIdHeader: adminRequestIdHeader, + }) - const closePromise = createServerClosedPromise(adminApp.server, () => { + const adminServerClosedPromise = createServerClosedPromise(adminApp.server, () => { logSchema.info(logger, '[Admin Server] Exited', { type: 'server', }) @@ -187,7 +228,7 @@ async function httpAdminServer( type: 'server', }) - await closePromise + await adminServerClosedPromise }, { once: true } ) @@ -204,49 +245,22 @@ async function httpAdminServer( return adminApp } -/** - * Starts HTTP S3 Server (no prefix) - * @param signal - */ -async function httpS3Server(signal: AbortSignal) { - const { s3Port, host, requestTraceHeader } = getConfig() - - const app: FastifyInstance = buildS3({ - loggerInstance: logger, - disableRequestLogging: true, - requestIdHeader: requestTraceHeader, - maxParamLength: 2500, - }) +export async function close() { + return shutdown(shutdownSignal) +} - const closePromise = createServerClosedPromise(app.server, () => { - logSchema.info(logger, '[S3 Server] Exited', { - type: 'server', - }) - }) +function registerPlatformaticCloseHandler() { + const platformatic = getGlobal() - try { - signal.addEventListener( - 'abort', - async () => { - logSchema.info(logger, '[S3 Server] Stopping', { - type: 'server', - }) - await closePromise - }, - { once: true } - ) - await app.listen({ port: s3Port, host, signal }) + if (!platformatic?.events) { + return + } - logSchema.info(logger, `[S3 Server] Listening on port ${s3Port}`, { - type: 'server', - }) + platformatic.events.on('close', () => { + void close() + }) +} - return app - } catch (err) { - logSchema.error(logger, `S3 Server failed to start`, { - type: 'serverStartError', - error: err, - }) - throw err - } +async function upgrades() { + return Promise.all([SyncCatalogIds.invoke({})]) } diff --git a/src/start/shutdown.ts b/src/start/shutdown.ts index cd571dfa6..c16c7ba20 100644 --- a/src/start/shutdown.ts +++ b/src/start/shutdown.ts @@ -1,8 +1,10 @@ -import { logger, logSchema } from '@internal/monitoring' import { AsyncAbortController } from '@internal/concurrency' import { multitenantKnex, TenantConnection } from '@internal/database' +import { logger, logSchema } from '@internal/monitoring' import http from 'http' +let shutdownPromise: Promise | undefined + /** * Binds shutdown handlers to the process * @param serverSignal @@ -18,23 +20,36 @@ export function bindShutdownSignals(serverSignal: AsyncAbortController) { }) // Shutdown handler - process.on('SIGTERM', async () => { - logSchema.info(logger, '[Server] Received SIGTERM, shutting down', { + let isShuttingDown = false + const gracefulShutdown = async (signal: string) => { + if (isShuttingDown) { + logSchema.info(logger, `[Server] Received ${signal} again, forcing exit`, { + type: 'shutdown', + }) + process.exit(1) + } + isShuttingDown = true + + logSchema.info(logger, `[Server] Received ${signal}, shutting down`, { type: 'shutdown', }) try { await shutdown(serverSignal) - logSchema.info(logger, '[Server] SIGTERM Shutdown successfully', { + logSchema.info(logger, `[Server] ${signal} Shutdown successfully`, { type: 'shutdown', }) + process.exit(0) } catch (e) { - logSchema.error(logger, '[Server] SIGTERM Shutdown with error', { + logSchema.error(logger, `[Server] ${signal} Shutdown with error`, { type: 'shutdown', error: e, }) process.exit(1) } - }) + } + + process.on('SIGTERM', () => gracefulShutdown('SIGTERM')) + process.on('SIGINT', () => gracefulShutdown('SIGINT')) } /** @@ -42,44 +57,52 @@ export function bindShutdownSignals(serverSignal: AsyncAbortController) { * @param serverSignal */ export async function shutdown(serverSignal: AsyncAbortController) { - try { - const errors: unknown[] = [] + if (shutdownPromise) { + return shutdownPromise + } - await serverSignal.abortAsync().catch((e) => { - logSchema.error(logger, 'Failed to abort server signal', { - type: 'shutdown', - error: e, + shutdownPromise = (async () => { + try { + const errors: unknown[] = [] + + await serverSignal.abortAsync().catch((e) => { + logSchema.error(logger, 'Failed to abort server signal', { + type: 'shutdown', + error: e, + }) + errors.push(e) }) - errors.push(e) - }) - await multitenantKnex.destroy().catch((e) => { - logSchema.error(logger, 'Failed to close database connection', { - type: 'shutdown', - error: e, + await multitenantKnex.destroy().catch((e) => { + logSchema.error(logger, 'Failed to close database connection', { + type: 'shutdown', + error: e, + }) + errors.push(e) + }) + + await TenantConnection.stop().catch((e) => { + logSchema.error(logger, 'Failed to close tenant connection', { + type: 'shutdown', + error: e, + }) }) - errors.push(e) - }) - await TenantConnection.stop().catch((e) => { - logSchema.error(logger, 'Failed to close tenant connection', { + if (errors.length > 0) { + throw errors[errors.length - 1] + } + } catch (e) { + logSchema.error(logger, 'shutdown error', { type: 'shutdown', error: e, }) - }) - - if (errors.length > 0) { - throw errors[errors.length - 1] + throw e + } finally { + logger.flush() } - } catch (e) { - logSchema.error(logger, 'shutdown error', { - type: 'shutdown', - error: e, - }) - throw e - } finally { - logger.flush() - } + })() + + return shutdownPromise } export function createServerClosedPromise(server: http.Server, cb: () => Promise | void) { diff --git a/src/start/worker.ts b/src/start/worker.ts index 2ea35ec75..979d1e2ae 100644 --- a/src/start/worker.ts +++ b/src/start/worker.ts @@ -1,13 +1,15 @@ -import { Queue } from '@internal/queue' -import { logger, logSchema } from '@internal/monitoring' -import { listenForTenantUpdate, PubSub } from '@internal/database' import { AsyncAbortController } from '@internal/concurrency' +import { listenForTenantUpdate, PubSub } from '@internal/database' +import { logger, logSchema, setLogger } from '@internal/monitoring' +import { Queue } from '@internal/queue' import { registerWorkers } from '@storage/events' - -import { getConfig } from '../config' import adminApp from '../admin-app' +import { getConfig } from '../config' import { bindShutdownSignals, createServerClosedPromise, shutdown } from './shutdown' +const workerLogger = logger.child({ service: 'worker' }) +setLogger(workerLogger) + const shutdownSignal = new AsyncAbortController() bindShutdownSignals(shutdownSignal) @@ -46,10 +48,13 @@ export async function main() { signal: shutdownSignal.signal, registerWorkers, onMessage: (job) => - logger.info(`[Worker] Job Received ${job.name} ${job.id}`, { - type: 'worker', - job: JSON.stringify(job), - }), + logger.info( + { + type: 'worker', + job: JSON.stringify(job), + }, + `[Worker] Job Received ${job.name} ${job.id}` + ), }), PubSub.start({ signal: shutdownSignal.nextGroup.nextGroup.signal, @@ -57,7 +62,7 @@ export async function main() { ]) const server = adminApp({ - logger, + loggerInstance: logger, disableRequestLogging: true, requestIdHeader: requestTraceHeader, }) diff --git a/src/storage/backend/adapter.ts b/src/storage/backend/adapter.ts index 944662290..b4cb61e40 100644 --- a/src/storage/backend/adapter.ts +++ b/src/storage/backend/adapter.ts @@ -31,6 +31,7 @@ export type ObjectMetadata = { eTag: string contentRange?: string httpStatusCode?: number + xRobotsTag?: string } export type UploadPart = { @@ -96,7 +97,8 @@ export abstract class StorageBackendAdapter { body: NodeJS.ReadableStream, contentType: string, cacheControl: string, - signal?: AbortSignal + signal?: AbortSignal, + contentLength?: number ): Promise { throw new Error('uploadObject not implemented') } diff --git a/src/storage/backend/file.test.ts b/src/storage/backend/file.test.ts new file mode 100644 index 000000000..de3442127 --- /dev/null +++ b/src/storage/backend/file.test.ts @@ -0,0 +1,470 @@ +import * as fsp from 'node:fs/promises' +import { removePath } from '@internal/fs' +import * as xattr from 'fs-xattr' +import os from 'os' +import path from 'path' +import { Readable } from 'stream' +import { type Mock, type MockInstance, vi } from 'vitest' +import { getConfig } from '../../config' +import { withOptionalVersion } from './adapter' +import { FileBackend } from './file' + +vi.mock('fs-xattr', () => ({ + set: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve(undefined)), +})) + +describe('FileBackend xattr metadata', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('uses a distinct linux xattr key for etag', async () => { + const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'storage-file-backend-')) + const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, 'platform') + const originalStoragePath = process.env.STORAGE_FILE_BACKEND_PATH + const originalFilePath = process.env.FILE_STORAGE_BACKEND_PATH + + try { + Object.defineProperty(process, 'platform', { + value: 'linux', + configurable: true, + }) + process.env.STORAGE_FILE_BACKEND_PATH = tmpDir + process.env.FILE_STORAGE_BACKEND_PATH = tmpDir + getConfig({ reload: true }) + + const backend = new FileBackend() + const uploadId = await backend.createMultiPartUpload( + 'bucket', + 'key', + 'v1', + 'text/plain', + 'no-cache' + ) + + await backend.uploadPart('bucket', 'key', 'v1', uploadId as string, 1, Readable.from('hello')) + + expect(xattr.set).toHaveBeenCalledWith( + expect.any(String), + 'user.supabase.etag', + expect.any(String) + ) + } finally { + if (originalPlatformDescriptor) { + Object.defineProperty(process, 'platform', originalPlatformDescriptor) + } + if (originalStoragePath === undefined) { + delete process.env.STORAGE_FILE_BACKEND_PATH + } else { + process.env.STORAGE_FILE_BACKEND_PATH = originalStoragePath + } + if (originalFilePath === undefined) { + delete process.env.FILE_STORAGE_BACKEND_PATH + } else { + process.env.FILE_STORAGE_BACKEND_PATH = originalFilePath + } + await removePath(tmpDir) + } + }) + + it('reads linux etag xattr during multipart completion', async () => { + const tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'storage-file-backend-')) + const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, 'platform') + const originalStoragePath = process.env.STORAGE_FILE_BACKEND_PATH + const originalFilePath = process.env.FILE_STORAGE_BACKEND_PATH + let uploadSpy: MockInstance | undefined + + try { + Object.defineProperty(process, 'platform', { + value: 'linux', + configurable: true, + }) + process.env.STORAGE_FILE_BACKEND_PATH = tmpDir + process.env.FILE_STORAGE_BACKEND_PATH = tmpDir + getConfig({ reload: true }) + + const backend = new FileBackend() + const uploadId = await backend.createMultiPartUpload( + 'bucket', + 'key', + 'v1', + 'text/plain', + 'no-cache' + ) + + const partDir = path.join( + tmpDir, + 'multiparts', + uploadId as string, + 'bucket', + withOptionalVersion('key', 'v1') + ) + const partPath = path.join(partDir, 'part-1') + await fsp.mkdir(partDir, { recursive: true }) + await fsp.writeFile(partPath, 'hello') + + const xattrGet = xattr.get as unknown as Mock + xattrGet.mockImplementation((_file: string, attribute: string) => { + if (attribute === 'user.supabase.etag') { + return Promise.resolve(Buffer.from('part-etag')) + } + return Promise.resolve(undefined) + }) + + uploadSpy = vi + .spyOn(backend, 'uploadObject') + .mockImplementation(async (_bucket, _key, _version, body) => { + await new Promise((resolve, reject) => { + body.on('error', reject) + body.on('end', resolve) + body.resume() + }) + return { + httpStatusCode: 200, + size: 5, + cacheControl: 'no-cache', + mimetype: 'text/plain', + eTag: '"final"', + lastModified: new Date(), + contentLength: 5, + } + }) + + await expect( + backend.completeMultipartUpload('bucket', 'key', uploadId as string, 'v1', [ + { PartNumber: 1, ETag: 'part-etag' }, + ]) + ).resolves.toMatchObject({ + ETag: '"final"', + }) + + expect(xattr.get).toHaveBeenCalledWith(expect.any(String), 'user.supabase.etag') + } finally { + uploadSpy?.mockRestore() + if (originalPlatformDescriptor) { + Object.defineProperty(process, 'platform', originalPlatformDescriptor) + } + if (originalStoragePath === undefined) { + delete process.env.STORAGE_FILE_BACKEND_PATH + } else { + process.env.STORAGE_FILE_BACKEND_PATH = originalStoragePath + } + if (originalFilePath === undefined) { + delete process.env.FILE_STORAGE_BACKEND_PATH + } else { + process.env.FILE_STORAGE_BACKEND_PATH = originalFilePath + } + await removePath(tmpDir) + } + }) +}) + +describe('FileBackend resolveSecurePath unit', () => { + let tmpDir: string + let backend: FileBackend + let originalStoragePath: string | undefined + let originalFilePath: string | undefined + + const resolveSecurePath = (relativePath: string) => + ( + backend as unknown as { + resolveSecurePath: (relativePath: string) => string + } + ).resolveSecurePath(relativePath) + + beforeEach(async () => { + tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'storage-file-backend-')) + originalStoragePath = process.env.STORAGE_FILE_BACKEND_PATH + originalFilePath = process.env.FILE_STORAGE_BACKEND_PATH + process.env.STORAGE_FILE_BACKEND_PATH = tmpDir + process.env.FILE_STORAGE_BACKEND_PATH = tmpDir + getConfig({ reload: true }) + backend = new FileBackend() + }) + + afterEach(async () => { + if (originalStoragePath === undefined) { + delete process.env.STORAGE_FILE_BACKEND_PATH + } else { + process.env.STORAGE_FILE_BACKEND_PATH = originalStoragePath + } + if (originalFilePath === undefined) { + delete process.env.FILE_STORAGE_BACKEND_PATH + } else { + process.env.FILE_STORAGE_BACKEND_PATH = originalFilePath + } + + await removePath(tmpDir) + }) + + it('resolves safe paths under storage root', () => { + expect(resolveSecurePath('bucket/folder/file.txt')).toBe( + path.join(tmpDir, 'bucket', 'folder', 'file.txt') + ) + }) + + it('rejects parent-directory segments even if they normalize within storage root', () => { + expect(() => resolveSecurePath('bucket/dir/../file.txt')).toThrow( + expect.objectContaining({ + code: 'InvalidKey', + }) + ) + }) + + it('allows double-dot in file names when not a path segment', () => { + expect(resolveSecurePath('bucket/file..name.txt')).toBe( + path.join(tmpDir, 'bucket', 'file..name.txt') + ) + }) + + it('rejects current-directory segments', () => { + expect(() => resolveSecurePath('.')).toThrow( + expect.objectContaining({ + code: 'InvalidKey', + }) + ) + }) + + it('rejects absolute paths', () => { + expect(() => resolveSecurePath('/tmp/escape.txt')).toThrow( + expect.objectContaining({ + code: 'InvalidKey', + }) + ) + }) + + it('rejects Windows absolute path formats', () => { + expect(() => resolveSecurePath('C:\\temp\\escape.txt')).toThrow( + expect.objectContaining({ + code: 'InvalidKey', + }) + ) + expect(() => resolveSecurePath('\\\\server\\share\\escape.txt')).toThrow( + expect.objectContaining({ + code: 'InvalidKey', + }) + ) + }) + + it('rejects null bytes', () => { + expect(() => resolveSecurePath('bucket/\0escape.txt')).toThrow( + expect.objectContaining({ + code: 'InvalidKey', + }) + ) + }) + + it('rejects traversal outside storage root', () => { + expect(() => resolveSecurePath('../escape.txt')).toThrow( + expect.objectContaining({ + code: 'InvalidKey', + }) + ) + }) +}) + +describe('FileBackend traversal protection', () => { + let tmpDir: string + let backend: FileBackend + let originalStoragePath: string | undefined + let originalFilePath: string | undefined + let escapePrefix: string + + beforeEach(async () => { + tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'storage-file-backend-')) + originalStoragePath = process.env.STORAGE_FILE_BACKEND_PATH + originalFilePath = process.env.FILE_STORAGE_BACKEND_PATH + process.env.STORAGE_FILE_BACKEND_PATH = tmpDir + process.env.FILE_STORAGE_BACKEND_PATH = tmpDir + getConfig({ reload: true }) + backend = new FileBackend() + escapePrefix = `storage-traversal-${Date.now()}-${Math.random().toString(36).slice(2)}` + }) + + afterEach(async () => { + if (originalStoragePath === undefined) { + delete process.env.STORAGE_FILE_BACKEND_PATH + } else { + process.env.STORAGE_FILE_BACKEND_PATH = originalStoragePath + } + if (originalFilePath === undefined) { + delete process.env.FILE_STORAGE_BACKEND_PATH + } else { + process.env.FILE_STORAGE_BACKEND_PATH = originalFilePath + } + + await removePath(tmpDir) + await removePath(path.join('/tmp', escapePrefix)) + }) + + it('rejects traversal key in multipart create with InvalidKey', async () => { + const traversalKey = `${'../'.repeat(20)}tmp/${escapePrefix}/multipart-escape.txt` + await expect( + backend.createMultiPartUpload('bucket', traversalKey, 'v1', 'text/plain', 'no-cache') + ).rejects.toMatchObject({ + code: 'InvalidKey', + }) + }) + + it('rejects traversal key in multipart upload-part with InvalidKey', async () => { + const traversalKey = `${'../'.repeat(20)}tmp/${escapePrefix}/multipart-escape.txt` + await expect( + backend.uploadPart('bucket', traversalKey, 'v1', 'upload-id', 1, Readable.from('escape-part')) + ).rejects.toMatchObject({ + code: 'InvalidKey', + }) + }) + + it('rejects traversal key in object operations with InvalidKey', async () => { + const traversalKey = `${'../'.repeat(20)}tmp/${escapePrefix}/object-escape.txt` + + await expect( + backend.uploadObject( + 'bucket', + traversalKey, + 'v1', + Readable.from('escape'), + 'text/plain', + 'no-cache' + ) + ).rejects.toMatchObject({ + code: 'InvalidKey', + }) + + await expect(backend.headObject('bucket', traversalKey, 'v1')).rejects.toMatchObject({ + code: 'InvalidKey', + }) + + await expect(backend.getObject('bucket', traversalKey, 'v1')).rejects.toMatchObject({ + code: 'InvalidKey', + }) + + await expect(backend.deleteObject('bucket', traversalKey, 'v1')).rejects.toMatchObject({ + code: 'InvalidKey', + }) + + await expect(backend.privateAssetUrl('bucket', traversalKey, 'v1')).rejects.toMatchObject({ + code: 'InvalidKey', + }) + }) + + it('rejects traversal key in copy/delete list operations with InvalidKey', async () => { + const traversalKey = `${'../'.repeat(20)}tmp/${escapePrefix}/copy-escape.txt` + + await backend.uploadObject( + 'bucket', + 'safe-source.txt', + 'v1', + Readable.from('safe-source'), + 'text/plain', + 'no-cache' + ) + + await expect( + backend.copyObject('bucket', 'safe-source.txt', 'v1', traversalKey, 'v2', {}) + ).rejects.toMatchObject({ + code: 'InvalidKey', + }) + + await expect(backend.deleteObjects('bucket', [traversalKey])).rejects.toMatchObject({ + code: 'InvalidKey', + }) + }) + + it('rejects traversal key in multipart auxiliary operations with InvalidKey', async () => { + const traversalDestKey = `${'../'.repeat(20)}tmp/${escapePrefix}/multipart-dest-escape.txt` + const traversalSourceKey = `${'../'.repeat(20)}tmp/${escapePrefix}/multipart-source-escape.txt` + + await expect( + backend.abortMultipartUpload('bucket', 'key', traversalDestKey) + ).rejects.toMatchObject({ + code: 'InvalidKey', + }) + + await expect( + backend.uploadPartCopy( + 'bucket', + traversalDestKey, + 'v1', + 'upload-id', + 1, + 'safe-source.txt', + 'v1' + ) + ).rejects.toMatchObject({ + code: 'InvalidKey', + }) + + await expect( + backend.uploadPartCopy( + 'bucket', + 'safe-dest.txt', + 'v1', + 'upload-id', + 1, + traversalSourceKey, + 'v1' + ) + ).rejects.toMatchObject({ + code: 'InvalidKey', + }) + }) +}) + +describe('FileBackend lastModified', () => { + let tmpDir: string + let backend: FileBackend + let originalStoragePath: string | undefined + let originalFilePath: string | undefined + + beforeEach(async () => { + tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'storage-file-backend-')) + originalStoragePath = process.env.STORAGE_FILE_BACKEND_PATH + originalFilePath = process.env.FILE_STORAGE_BACKEND_PATH + process.env.STORAGE_FILE_BACKEND_PATH = tmpDir + process.env.FILE_STORAGE_BACKEND_PATH = tmpDir + getConfig({ reload: true }) + backend = new FileBackend() + }) + + afterEach(async () => { + if (originalStoragePath === undefined) { + delete process.env.STORAGE_FILE_BACKEND_PATH + } else { + process.env.STORAGE_FILE_BACKEND_PATH = originalStoragePath + } + if (originalFilePath === undefined) { + delete process.env.FILE_STORAGE_BACKEND_PATH + } else { + process.env.FILE_STORAGE_BACKEND_PATH = originalFilePath + } + await removePath(tmpDir) + }) + + it('headObject/getObject should return mtime as lastModified', async () => { + const bucket = 'test-bucket' + const key = 'test-file.txt' + const version = 'v1' + + await backend.uploadObject( + bucket, + key, + version, + Readable.from('initial content'), + 'text/plain', + 'no-cache' + ) + + const filePath = path.join(tmpDir, withOptionalVersion(`${bucket}/${key}`, version)) + const stat = await fsp.stat(filePath) + const knownMtime = new Date(stat.birthtimeMs + 60_000) // mtime must be in the future + await fsp.utimes(filePath, knownMtime, knownMtime) + + const headResult = await backend.headObject(bucket, key, version) + expect(headResult.lastModified).toEqual(knownMtime) + + const getResult = await backend.getObject(bucket, key, version) + expect(getResult.metadata.lastModified).toEqual(knownMtime) + }) +}) diff --git a/src/storage/backend/file.ts b/src/storage/backend/file.ts index 44e14e98e..13d236b9b 100644 --- a/src/storage/backend/file.ts +++ b/src/storage/backend/file.ts @@ -1,22 +1,23 @@ +import type { Stats } from 'node:fs' +import fs from 'node:fs' +import * as fsp from 'node:fs/promises' +import { ERRORS, StorageBackendError } from '@internal/errors' +import { ensureDir, ensureFile, pathExists, removePath } from '@internal/fs' +import { createHash, randomUUID } from 'crypto' import * as xattr from 'fs-xattr' -import fs from 'fs-extra' import path from 'path' -import fileChecksum from 'md5-file' -import { promisify } from 'util' import stream from 'stream' -import MultiStream from 'multistream' +import { promisify } from 'util' import { getConfig } from '../../config' import { - StorageBackendAdapter, + BrowserCacheHeaders, ObjectMetadata, ObjectResponse, - withOptionalVersion, - BrowserCacheHeaders, + StorageBackendAdapter, UploadPart, + withOptionalVersion, } from './adapter' -import { ERRORS, StorageBackendError } from '@internal/errors' -import { randomUUID } from 'crypto' -import fsExtra from 'fs-extra' + const pipeline = promisify(stream.pipeline) interface FileMetadata { @@ -34,7 +35,7 @@ const METADATA_ATTR_KEYS = { linux: { 'cache-control': 'user.supabase.cache-control', 'content-type': 'user.supabase.content-type', - etag: 'user.supabase.content-type', + etag: 'user.supabase.etag', }, } @@ -84,20 +85,19 @@ export class FileBackend implements StorageBackendAdapter { headers?: BrowserCacheHeaders ): Promise { // 'Range: bytes=#######-###### - const file = path.resolve(this.filePath, withOptionalVersion(`${bucketName}/${key}`, version)) - const data = await fs.stat(file) + const file = this.resolveSecurePath(withOptionalVersion(`${bucketName}/${key}`, version)) + const data = await fsp.stat(file) const eTag = await this.etag(file, data) const fileSize = data.size const { cacheControl, contentType } = await this.getFileMetadata(file) - const lastModified = new Date(0) - lastModified.setUTCMilliseconds(data.mtimeMs) + const lastModified = data.mtime if (headers?.ifNoneMatch && headers.ifNoneMatch === eTag) { return { metadata: { cacheControl: cacheControl || 'no-cache', mimetype: contentType || 'application/octet-stream', - lastModified: lastModified, + lastModified, httpStatusCode: 304, size: data.size, eTag, @@ -115,7 +115,7 @@ export class FileBackend implements StorageBackendAdapter { metadata: { cacheControl: cacheControl || 'no-cache', mimetype: contentType || 'application/octet-stream', - lastModified: lastModified, + lastModified, httpStatusCode: 304, size: data.size, eTag, @@ -139,10 +139,10 @@ export class FileBackend implements StorageBackendAdapter { metadata: { cacheControl: cacheControl || 'no-cache', mimetype: contentType || 'application/octet-stream', - lastModified: lastModified, + lastModified, contentRange: `bytes ${startRange}-${endRange}/${fileSize}`, httpStatusCode: 206, - size: size, + size, eTag, contentLength: chunkSize, }, @@ -155,7 +155,7 @@ export class FileBackend implements StorageBackendAdapter { metadata: { cacheControl: cacheControl || 'no-cache', mimetype: contentType || 'application/octet-stream', - lastModified: lastModified, + lastModified, httpStatusCode: 200, size: data.size, eTag, @@ -182,11 +182,13 @@ export class FileBackend implements StorageBackendAdapter { version: string | undefined, body: NodeJS.ReadableStream, contentType: string, - cacheControl: string + cacheControl: string, + signal?: AbortSignal, + contentLength?: number ): Promise { try { - const file = path.resolve(this.filePath, withOptionalVersion(`${bucketName}/${key}`, version)) - await fs.ensureFile(file) + const file = this.resolveSecurePath(withOptionalVersion(`${bucketName}/${key}`, version)) + await ensureFile(file) const destFile = fs.createWriteStream(file) await pipeline(body, destFile) @@ -202,6 +204,9 @@ export class FileBackend implements StorageBackendAdapter { httpStatusCode: 200, } } catch (err: any) { + if (err instanceof StorageBackendError) { + throw err + } throw StorageBackendError.fromError(err) } } @@ -214,8 +219,8 @@ export class FileBackend implements StorageBackendAdapter { */ async deleteObject(bucket: string, key: string, version: string | undefined): Promise { try { - const file = path.resolve(this.filePath, withOptionalVersion(`${bucket}/${key}`, version)) - await fs.remove(file) + const file = this.resolveSecurePath(withOptionalVersion(`${bucket}/${key}`, version)) + await removePath(file) // Clean up empty parent directories await this.cleanupEmptyDirectories(path.dirname(file)) @@ -246,19 +251,18 @@ export class FileBackend implements StorageBackendAdapter { destinationVersion: string, metadata: { cacheControl?: string; contentType?: string } ): Promise> { - const srcFile = path.resolve(this.filePath, withOptionalVersion(`${bucket}/${source}`, version)) - const destFile = path.resolve( - this.filePath, + const srcFile = this.resolveSecurePath(withOptionalVersion(`${bucket}/${source}`, version)) + const destFile = this.resolveSecurePath( withOptionalVersion(`${bucket}/${destination}`, destinationVersion) ) - await fs.ensureFile(destFile) - await fs.copyFile(srcFile, destFile) + await ensureFile(destFile) + await fsp.copyFile(srcFile, destFile) const originalMetadata = await this.getFileMetadata(srcFile) await this.setFileMetadata(destFile, Object.assign({}, originalMetadata, metadata)) - const fileStat = await fs.lstat(destFile) + const fileStat = await fsp.lstat(destFile) const eTag = await this.etag(destFile, fileStat) return { @@ -275,7 +279,7 @@ export class FileBackend implements StorageBackendAdapter { */ async deleteObjects(bucket: string, prefixes: string[]): Promise { const promises = prefixes.map((prefix) => { - return fs.rm(path.resolve(this.filePath, bucket, prefix)) + return removePath(this.resolveSecurePath(`${bucket}/${prefix}`)) }) const results = await Promise.allSettled(promises) @@ -284,13 +288,10 @@ export class FileBackend implements StorageBackendAdapter { results.forEach((result, index) => { if (result.status === 'rejected') { - if (result.reason.code === 'ENOENT') { - return - } throw result.reason } else { // Add parent directory of successfully deleted file - const filePath = path.resolve(this.filePath, bucket, prefixes[index]) + const filePath = this.resolveSecurePath(`${bucket}/${prefixes[index]}`) parentDirs.add(path.dirname(filePath)) } }) @@ -299,7 +300,7 @@ export class FileBackend implements StorageBackendAdapter { for (const dir of parentDirs) { try { await this.cleanupEmptyDirectories(dir) - } catch (e) { + } catch { // Ignore cleanup errors to not affect the main deletion operation } } @@ -316,12 +317,11 @@ export class FileBackend implements StorageBackendAdapter { key: string, version: string | undefined ): Promise { - const file = path.join(this.filePath, withOptionalVersion(`${bucket}/${key}`, version)) + const file = this.resolveSecurePath(withOptionalVersion(`${bucket}/${key}`, version)) - const data = await fs.stat(file) + const data = await fsp.stat(file) const { cacheControl, contentType } = await this.getFileMetadata(file) - const lastModified = new Date(0) - lastModified.setUTCMilliseconds(data.mtimeMs) + const lastModified = data.mtime const eTag = await this.etag(file, data) return { @@ -330,7 +330,7 @@ export class FileBackend implements StorageBackendAdapter { cacheControl: cacheControl || 'no-cache', mimetype: contentType || 'application/octet-stream', eTag, - lastModified: data.birthtime, + lastModified, contentLength: data.size, } } @@ -343,17 +343,20 @@ export class FileBackend implements StorageBackendAdapter { cacheControl: string ): Promise { const uploadId = randomUUID() - const multiPartFolder = path.join( - this.filePath, - 'multiparts', - uploadId, - bucketName, - withOptionalVersion(key, version) + const multiPartFolder = this.resolveSecurePath( + path.join('multiparts', uploadId, bucketName, withOptionalVersion(key, version)) ) - - const multipartFile = path.join(multiPartFolder, 'metadata.json') - await fsExtra.ensureDir(multiPartFolder) - await fsExtra.writeFile(multipartFile, JSON.stringify({ contentType, cacheControl })) + const multipartFile = this.resolveSecurePath( + path.join( + 'multiparts', + uploadId, + bucketName, + withOptionalVersion(key, version), + 'metadata.json' + ) + ) + await ensureDir(multiPartFolder) + await fsp.writeFile(multipartFile, JSON.stringify({ contentType, cacheControl })) return uploadId } @@ -366,23 +369,23 @@ export class FileBackend implements StorageBackendAdapter { partNumber: number, body: stream.Readable ): Promise<{ ETag?: string }> { - const multiPartFolder = path.join( - this.filePath, - 'multiparts', - uploadId, - bucketName, - withOptionalVersion(key, version) + const partPath = this.resolveSecurePath( + path.join( + 'multiparts', + uploadId, + bucketName, + withOptionalVersion(key, version), + `part-${partNumber}` + ) ) - const partPath = path.join(multiPartFolder, `part-${partNumber}`) - - const writeStream = fsExtra.createWriteStream(partPath) + const writeStream = fs.createWriteStream(partPath) await pipeline(body, writeStream) - const etag = await fileChecksum(partPath) + const etag = await this.computeMd5(partPath) - const platform = process.platform == 'darwin' ? 'darwin' : 'linux' + const platform = process.platform === 'darwin' ? 'darwin' : 'linux' await this.setMetadataAttr(partPath, METADATA_ATTR_KEYS[platform]['etag'], etag) return { ETag: etag } @@ -401,20 +404,20 @@ export class FileBackend implements StorageBackendAdapter { version: string } > { - const multiPartFolder = path.join( - this.filePath, - 'multiparts', - uploadId, - bucketName, - withOptionalVersion(key, version) - ) - const partsByEtags = parts.map(async (part) => { - const partFilePath = path.join(multiPartFolder, `part-${part.PartNumber}`) - const partExists = await fsExtra.pathExists(partFilePath) + const partFilePath = this.resolveSecurePath( + path.join( + 'multiparts', + uploadId, + bucketName, + withOptionalVersion(key, version), + `part-${part.PartNumber}` + ) + ) + const partExists = await pathExists(partFilePath) if (partExists) { - const platform = process.platform == 'darwin' ? 'darwin' : 'linux' + const platform = process.platform === 'darwin' ? 'darwin' : 'linux' const etag = await this.getMetadataAttr(partFilePath, METADATA_ATTR_KEYS[platform]['etag']) if (etag === part.ETag) { return partFilePath @@ -428,13 +431,17 @@ export class FileBackend implements StorageBackendAdapter { const finalParts = await Promise.all(partsByEtags) finalParts.sort((a, b) => parseInt(a.split('-')[1]) - parseInt(b.split('-')[1])) - const fileStreams = finalParts.map((partPath) => { - return fs.createReadStream(partPath) - }) - - const multistream = new MultiStream(fileStreams) - const metadataContent = await fsExtra.readFile( - path.join(multiPartFolder, 'metadata.json'), + const multipartStream = this.mergePartStreams(finalParts) + const metadataContent = await fsp.readFile( + this.resolveSecurePath( + path.join( + 'multiparts', + uploadId, + bucketName, + withOptionalVersion(key, version), + 'metadata.json' + ) + ), 'utf-8' ) @@ -444,17 +451,17 @@ export class FileBackend implements StorageBackendAdapter { bucketName, key, version, - multistream, + multipartStream, metadata.contentType, metadata.cacheControl ) - fsExtra.remove(path.join(this.filePath, 'multiparts', uploadId)).catch(() => { + removePath(this.resolveSecurePath(path.join('multiparts', uploadId))).catch(() => { // no-op }) return { - version: version, + version, ETag: uploaded.eTag, bucket: bucketName, location: `${bucketName}/${key}`, @@ -467,14 +474,14 @@ export class FileBackend implements StorageBackendAdapter { uploadId: string, version?: string ): Promise { - const multiPartFolder = path.join(this.filePath, 'multiparts', uploadId) + const multiPartFolder = this.resolveSecurePath(path.join('multiparts', uploadId)) - await fsExtra.remove(multiPartFolder) + await removePath(multiPartFolder) // Clean up empty parent directories try { await this.cleanupEmptyDirectories(path.dirname(multiPartFolder)) - } catch (e) { + } catch { // Ignore cleanup errors } } @@ -489,22 +496,20 @@ export class FileBackend implements StorageBackendAdapter { sourceVersion?: string, rangeBytes?: { fromByte: number; toByte: number } ): Promise<{ eTag?: string; lastModified?: Date }> { - const multiPartFolder = path.join( - this.filePath, - 'multiparts', - UploadId, - storageS3Bucket, - withOptionalVersion(key, version) + const partFilePath = this.resolveSecurePath( + path.join( + 'multiparts', + UploadId, + storageS3Bucket, + withOptionalVersion(key, version), + `part-${PartNumber}` + ) ) - - const partFilePath = path.join(multiPartFolder, `part-${PartNumber}`) - const sourceFilePath = path.join( - this.filePath, - storageS3Bucket, - withOptionalVersion(sourceKey, sourceVersion) + const sourceFilePath = this.resolveSecurePath( + `${storageS3Bucket}/${withOptionalVersion(sourceKey, sourceVersion)}` ) - const platform = process.platform == 'darwin' ? 'darwin' : 'linux' + const platform = process.platform === 'darwin' ? 'darwin' : 'linux' const readStreamOptions = rangeBytes ? { start: rangeBytes.fromByte, end: rangeBytes.toByte } @@ -514,10 +519,10 @@ export class FileBackend implements StorageBackendAdapter { const writePart = fs.createWriteStream(partFilePath) await pipeline(partStream, writePart) - const etag = await fileChecksum(partFilePath) + const etag = await this.computeMd5(partFilePath) await this.setMetadataAttr(partFilePath, METADATA_ATTR_KEYS[platform]['etag'], etag) - const fileStat = await fs.lstat(partFilePath) + const fileStat = await fsp.lstat(partFilePath) return { eTag: etag, @@ -525,6 +530,30 @@ export class FileBackend implements StorageBackendAdapter { } } + private mergePartStreams(partPaths: string[]): stream.Readable { + return stream.Readable.from(this.iteratePartChunks(partPaths)) + } + + private async *iteratePartChunks(partPaths: string[]): AsyncGenerator { + for (const partPath of partPaths) { + const partStream = fs.createReadStream(partPath) + for await (const chunk of partStream) { + yield chunk as Buffer + } + } + } + + private async computeMd5(filePath: string): Promise { + const hash = createHash('md5') + const readStream = fs.createReadStream(filePath) + + for await (const chunk of readStream) { + hash.update(chunk) + } + + return hash.digest('hex') + } + /** * Returns a private url that can only be accessed internally by the system * @param bucket @@ -532,11 +561,11 @@ export class FileBackend implements StorageBackendAdapter { * @param version */ async privateAssetUrl(bucket: string, key: string, version: string | undefined): Promise { - return 'local:///' + path.join(this.filePath, withOptionalVersion(`${bucket}/${key}`, version)) + return 'local:///' + this.resolveSecurePath(withOptionalVersion(`${bucket}/${key}`, version)) } async setFileMetadata(file: string, { contentType, cacheControl }: FileMetadata) { - const platform = process.platform == 'darwin' ? 'darwin' : 'linux' + const platform = process.platform === 'darwin' ? 'darwin' : 'linux' await Promise.all([ this.setMetadataAttr(file, METADATA_ATTR_KEYS[platform]['cache-control'], cacheControl), this.setMetadataAttr(file, METADATA_ATTR_KEYS[platform]['content-type'], contentType), @@ -548,7 +577,7 @@ export class FileBackend implements StorageBackendAdapter { } protected async getFileMetadata(file: string) { - const platform = process.platform == 'darwin' ? 'darwin' : 'linux' + const platform = process.platform === 'darwin' ? 'darwin' : 'linux' const [cacheControl, contentType] = await Promise.all([ this.getMetadataAttr(file, METADATA_ATTR_KEYS[platform]['cache-control']), this.getMetadataAttr(file, METADATA_ATTR_KEYS[platform]['content-type']), @@ -577,12 +606,12 @@ export class FileBackend implements StorageBackendAdapter { */ protected async isEmptyDirectory(dirPath: string): Promise { try { - const directory = await fs.opendir(dirPath) + const directory = await fsp.opendir(dirPath) const entry = await directory.read() await directory.close() return entry === null - } catch (error) { + } catch { return false } } @@ -599,7 +628,7 @@ export class FileBackend implements StorageBackendAdapter { } // Check if directory exists - const exists = await fs.pathExists(dirPath) + const exists = await pathExists(dirPath) if (!exists) { return } @@ -608,13 +637,13 @@ export class FileBackend implements StorageBackendAdapter { const isEmpty = await this.isEmptyDirectory(dirPath) if (isEmpty) { // Remove empty directory - using fs.remove for better cross-platform compatibility - await fs.remove(dirPath) + await removePath(dirPath) // Recursively check parent directory const parentDir = path.dirname(dirPath) await this.cleanupEmptyDirectories(parentDir) } - } catch (e: any) { + } catch { // Ignore errors during cleanup to not affect main operations // Could be permission issues, concurrent access, directory not empty due to race conditions, etc. // Optional: Log for debugging purposes (uncomment if needed) @@ -622,9 +651,51 @@ export class FileBackend implements StorageBackendAdapter { } } - private async etag(file: string, stats: fs.Stats): Promise { + /** + * Securely resolves a path within the storage directory, preventing path traversal attacks + * @param relativePath The relative path to resolve + * @throws {StorageBackendError} If the resolved path escapes the storage directory + */ + private resolveSecurePath(relativePath: string): string { + if (relativePath.includes('\0')) { + throw ERRORS.InvalidKey(`Invalid key: ${relativePath} contains null byte`) + } + + if (path.isAbsolute(relativePath)) { + throw ERRORS.InvalidKey(`Invalid key: ${relativePath} must be a relative path`) + } + + const isWindowsDriveAbsolutePath = /^[a-zA-Z]:[\\/]/.test(relativePath) + const isWindowsUncPath = /^\\\\[^\\/]+[\\/][^\\/]+/.test(relativePath) + if (isWindowsDriveAbsolutePath || isWindowsUncPath) { + throw ERRORS.InvalidKey(`Invalid key: ${relativePath} must not be an absolute Windows path`) + } + + const hasDotTraversalSegment = relativePath + .split(/[\\/]+/) + .filter(Boolean) + .some((segment) => segment === '.' || segment === '..') + + if (hasDotTraversalSegment) { + throw ERRORS.InvalidKey(`Path traversal detected: ${relativePath} contains dot path segment`) + } + + const resolvedPath = path.resolve(this.filePath, relativePath) + const normalizedPath = path.normalize(resolvedPath) + + // Ensure the resolved path is within the storage directory + if (!normalizedPath.startsWith(this.filePath + path.sep) && normalizedPath !== this.filePath) { + throw ERRORS.InvalidKey( + `Path traversal detected: ${relativePath} resolves outside storage directory` + ) + } + + return normalizedPath + } + + private async etag(file: string, stats: Stats): Promise { if (this.etagAlgorithm === 'md5') { - const checksum = await fileChecksum(file) + const checksum = await this.computeMd5(file) return `"${checksum}"` } else if (this.etagAlgorithm === 'mtime') { return `"${stats.mtimeMs.toString(16)}-${stats.size.toString(16)}"` diff --git a/src/storage/backend/index.ts b/src/storage/backend/index.ts index 46e5b5a40..728656147 100644 --- a/src/storage/backend/index.ts +++ b/src/storage/backend/index.ts @@ -1,11 +1,11 @@ +import { getConfig, StorageBackendType } from '../../config' import { StorageBackendAdapter } from './adapter' import { FileBackend } from './file' import { S3Backend, S3ClientOptions } from './s3/adapter' -import { getConfig, StorageBackendType } from '../../config' -export * from './s3' -export * from './file' export * from './adapter' +export * from './file' +export * from './s3' const { storageS3Region, storageS3Endpoint, storageS3ForcePathStyle, storageS3ClientTimeout } = getConfig() diff --git a/src/storage/backend/s3/adapter.test.ts b/src/storage/backend/s3/adapter.test.ts new file mode 100644 index 000000000..bc86db51b --- /dev/null +++ b/src/storage/backend/s3/adapter.test.ts @@ -0,0 +1,329 @@ +import { HeadObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3' +import { Upload } from '@aws-sdk/lib-storage' +import { ErrorCode, isStorageError } from '@internal/errors' +import { Readable } from 'stream' +import { type Mock, vi } from 'vitest' +import { getConfig } from '../../../config' +import { MAX_PUT_OBJECT_SIZE, S3Backend } from './adapter' + +vi.mock('@aws-sdk/client-s3', async () => { + const originalModule = + await vi.importActual('@aws-sdk/client-s3') + return { + ...originalModule, + S3Client: vi.fn(function () { + return { + send: vi.fn(), + } + }), + } +}) + +vi.mock('@aws-sdk/lib-storage', async () => { + const originalModule = + await vi.importActual('@aws-sdk/lib-storage') + return { + ...originalModule, + Upload: vi.fn(function () {}), + } +}) + +type UploadOptionsShape = { + queueSize?: number +} + +type MockUploadDoneResult = { + ETag: string + $metadata: { + httpStatusCode: number + } +} + +type MockUploadInstance = { + options: UploadOptionsShape + abort: Mock + done: Mock<() => Promise> + on: Mock + off: Mock + emit: (event: string, payload: unknown) => void +} + +describe('S3Backend', () => { + let mockSend: Mock + let mockUploadDone: Mock<(instance: MockUploadInstance) => Promise> + let uploadInstances: MockUploadInstance[] + + beforeEach(() => { + vi.clearAllMocks() + mockSend = vi.fn() + mockUploadDone = vi.fn().mockResolvedValue({ + ETag: '"multipart-etag"', + $metadata: { + httpStatusCode: 200, + }, + }) + uploadInstances = [] + + ;(S3Client as unknown as Mock).mockImplementation(function () { + return { + send: mockSend, + } + }) + + ;(Upload as unknown as Mock).mockImplementation(function (options: UploadOptionsShape) { + const handlers = new Map void>>() + const instance = {} as MockUploadInstance + + instance.options = options + instance.abort = vi.fn() + instance.done = vi.fn(() => mockUploadDone(instance)) + instance.on = vi.fn((event: string, handler: (payload: unknown) => void) => { + const eventHandlers = handlers.get(event) ?? new Set() + eventHandlers.add(handler) + handlers.set(event, eventHandlers) + return instance + }) + instance.off = vi.fn((event: string, handler: (payload: unknown) => void) => { + handlers.get(event)?.delete(handler) + return instance + }) + instance.emit = (event: string, payload: unknown) => { + handlers.get(event)?.forEach((handler) => handler(payload)) + } + + uploadInstances.push(instance) + return instance + }) + }) + + function createBackend() { + return new S3Backend({ + region: 'us-east-1', + endpoint: 'http://localhost:9000', + }) + } + + describe('getObject', () => { + test('should return correct default MIME type when S3 returns no ContentType', async () => { + mockSend.mockResolvedValue({ + Body: Readable.from(['test content']), + CacheControl: 'max-age=3600', + ETag: '"abc123"', + LastModified: new Date('2024-01-01'), + ContentLength: 12, + $metadata: { + httpStatusCode: 200, + }, + }) + + const backend = createBackend() + + const result = await backend.getObject('test-bucket', 'test-key', undefined) + + expect(result.metadata.mimetype).toBe('application/octet-stream') + expect(result.metadata.cacheControl).toBe('max-age=3600') + expect(result.metadata.eTag).toBe('"abc123"') + expect(result.httpStatusCode).toBe(200) + }) + + test('should use ContentType from S3 when provided', async () => { + mockSend.mockResolvedValue({ + Body: Readable.from(['test content']), + ContentType: 'image/png', + CacheControl: 'no-cache', + ETag: '"def456"', + LastModified: new Date('2024-01-01'), + ContentLength: 12, + $metadata: { + httpStatusCode: 200, + }, + }) + + const backend = createBackend() + + const result = await backend.getObject('test-bucket', 'test-key', undefined) + + expect(result.metadata.mimetype).toBe('image/png') + }) + }) + + describe('uploadObject', () => { + test('uses PutObject for known-size uploads within the single-request limit', async () => { + mockSend.mockResolvedValue({ + ETag: '"put-etag"', + $metadata: { + httpStatusCode: 200, + }, + }) + + const backend = createBackend() + const result = await backend.uploadObject( + 'test-bucket', + 'test-key', + undefined, + Readable.from(['hello']), + 'text/plain', + 'max-age=60', + undefined, + 5 + ) + + expect(mockSend).toHaveBeenCalledTimes(1) + expect(mockSend.mock.calls[0][0]).toBeInstanceOf(PutObjectCommand) + expect(mockSend.mock.calls[0][0].input).toMatchObject({ + Bucket: 'test-bucket', + Key: 'test-key', + ContentType: 'text/plain', + CacheControl: 'max-age=60', + ContentLength: 5, + }) + expect(Upload).not.toHaveBeenCalled() + expect(result).toMatchObject({ + httpStatusCode: 200, + cacheControl: 'max-age=60', + eTag: '"put-etag"', + mimetype: 'text/plain', + contentLength: 5, + size: 5, + }) + }) + + test('uses PutObject for zero-byte uploads when content length is known', async () => { + mockSend.mockResolvedValue({ + ETag: '"empty-etag"', + $metadata: { + httpStatusCode: 200, + }, + }) + + const backend = createBackend() + const result = await backend.uploadObject( + 'test-bucket', + 'empty-key', + undefined, + Readable.from([]), + 'application/octet-stream', + 'no-cache', + undefined, + 0 + ) + + expect(mockSend).toHaveBeenCalledTimes(1) + expect(mockSend.mock.calls[0][0]).toBeInstanceOf(PutObjectCommand) + expect(mockSend.mock.calls[0][0].input).toMatchObject({ + Bucket: 'test-bucket', + Key: 'empty-key', + ContentType: 'application/octet-stream', + CacheControl: 'no-cache', + ContentLength: 0, + }) + expect(Upload).not.toHaveBeenCalled() + expect(result).toMatchObject({ + httpStatusCode: 200, + cacheControl: 'no-cache', + eTag: '"empty-etag"', + mimetype: 'application/octet-stream', + contentLength: 0, + size: 0, + }) + }) + + test('falls back to multipart upload when content length exceeds the single-request limit', async () => { + const overLimit = MAX_PUT_OBJECT_SIZE + 1 + const lastModified = new Date('2024-01-01T00:00:00.000Z') + + mockUploadDone.mockImplementationOnce(async (instance) => { + instance.emit('httpUploadProgress', { loaded: 1 }) + return { + ETag: '"multipart-etag"', + $metadata: { + httpStatusCode: 200, + }, + } + }) + mockSend.mockResolvedValueOnce({ + CacheControl: 'max-age=60', + ContentType: 'text/plain', + ContentLength: overLimit, + ETag: '"head-etag"', + LastModified: lastModified, + $metadata: { + httpStatusCode: 200, + }, + }) + + const backend = createBackend() + const result = await backend.uploadObject( + 'test-bucket', + 'test-key', + undefined, + Readable.from(['hello']), + 'text/plain', + 'max-age=60', + undefined, + overLimit + ) + + expect(Upload).toHaveBeenCalledTimes(1) + expect(uploadInstances[0].options.queueSize).toBe(getConfig().storageS3UploadQueueSize) + expect(mockSend).toHaveBeenCalledTimes(1) + expect(mockSend.mock.calls[0][0]).toBeInstanceOf(HeadObjectCommand) + expect(result).toMatchObject({ + httpStatusCode: 200, + cacheControl: 'max-age=60', + eTag: '"head-etag"', + mimetype: 'text/plain', + contentLength: overLimit, + size: overLimit, + lastModified, + }) + }) + + test('uses multipart upload when content length is unknown', async () => { + const backend = createBackend() + const result = await backend.uploadObject( + 'test-bucket', + 'test-key', + undefined, + Readable.from(['hello']), + 'text/plain', + 'max-age=60' + ) + + expect(Upload).toHaveBeenCalledTimes(1) + expect(uploadInstances[0].options.queueSize).toBe(getConfig().storageS3UploadQueueSize) + expect(mockSend).not.toHaveBeenCalled() + expect(result).toMatchObject({ + httpStatusCode: 200, + cacheControl: 'max-age=60', + eTag: '"multipart-etag"', + mimetype: 'text/plain', + contentLength: 0, + size: 0, + }) + }) + + test('maps PutObject abort errors to AbortedTerminate', async () => { + mockSend.mockRejectedValueOnce(Object.assign(new Error('aborted'), { name: 'AbortError' })) + + const backend = createBackend() + + try { + await backend.uploadObject( + 'test-bucket', + 'test-key', + undefined, + Readable.from(['hello']), + 'text/plain', + 'max-age=60', + undefined, + 5 + ) + throw new Error('Expected uploadObject to throw') + } catch (error) { + expect(isStorageError(ErrorCode.AbortedTerminate, error)).toBe(true) + expect((error as Error).message).toBe('Upload was aborted') + } + }) + }) +}) diff --git a/src/storage/backend/s3/adapter.ts b/src/storage/backend/s3/adapter.ts index c1bd3a718..d060539ef 100644 --- a/src/storage/backend/s3/adapter.ts +++ b/src/storage/backend/s3/adapter.ts @@ -1,40 +1,48 @@ +import { Readable } from 'node:stream' import { AbortMultipartUploadCommand, CompleteMultipartUploadCommand, CopyObjectCommand, CreateMultipartUploadCommand, DeleteObjectCommand, + DeleteObjectsCommand, GetObjectCommand, GetObjectCommandInput, HeadObjectCommand, ListObjectsV2Command, ListPartsCommand, + PutObjectCommand, S3Client, S3ClientConfig, UploadPartCommand, UploadPartCopyCommand, } from '@aws-sdk/client-s3' import { Progress, Upload } from '@aws-sdk/lib-storage' +import { getSignedUrl } from '@aws-sdk/s3-request-presigner' +import { ERRORS, StorageBackendError } from '@internal/errors' +import { createAgent, InstrumentedAgent } from '@internal/http' +import { monitorStream } from '@internal/streams' import { NodeHttpHandler } from '@smithy/node-http-handler' +import { BackupObjectInfo, ObjectBackup } from '@storage/backend/s3/backup' +import { getConfig } from '../../../config' import { - StorageBackendAdapter, BrowserCacheHeaders, ObjectMetadata, ObjectResponse, - withOptionalVersion, + StorageBackendAdapter, UploadPart, + withOptionalVersion, } from './../adapter' -import { getSignedUrl } from '@aws-sdk/s3-request-presigner' -import { ERRORS, StorageBackendError } from '@internal/errors' -import { getConfig } from '../../../config' -import { Readable } from 'node:stream' -import { createAgent, InstrumentedAgent } from '@internal/http' -import { logger } from '@internal/monitoring' -import { monitorStream } from '@internal/streams' -import { BackupObjectInfo, ObjectBackup } from '@storage/backend/s3/backup' -const { tracingFeatures, storageS3MaxSockets, tracingEnabled, storageS3DeleteConcurrency } = - getConfig() +const { + storageS3UploadQueueSize, + tracingFeatures, + storageS3MaxSockets, + tracingEnabled, + storageS3DisableChecksum, +} = getConfig() + +export const MAX_PUT_OBJECT_SIZE = 5 * 1024 * 1024 * 1024 // 5GB export interface S3ClientOptions { endpoint?: string @@ -106,7 +114,7 @@ export class S3Backend implements StorageBackendAdapter { return { metadata: { cacheControl: data.CacheControl || 'no-cache', - mimetype: data.ContentType || 'application/octa-stream', + mimetype: data.ContentType || 'application/octet-stream', eTag: data.ETag || '', lastModified: data.LastModified, contentRange: data.ContentRange, @@ -121,6 +129,7 @@ export class S3Backend implements StorageBackendAdapter { /** * Uploads and store an object + * Max 5GB * @param bucketName * @param key * @param version @@ -128,6 +137,7 @@ export class S3Backend implements StorageBackendAdapter { * @param contentType * @param cacheControl * @param signal + * @param contentLength */ async uploadObject( bucketName: string, @@ -136,16 +146,99 @@ export class S3Backend implements StorageBackendAdapter { body: Readable, contentType: string, cacheControl: string, - signal?: AbortSignal + signal?: AbortSignal, + contentLength?: number ): Promise { if (signal?.aborted) { throw ERRORS.Aborted('Upload was aborted') } + if (typeof contentLength !== 'number' || contentLength > MAX_PUT_OBJECT_SIZE) { + // Use multipart when the length is unknown or exceeds S3's 5GB single-request limit. + return this.bufferedMultipartUpload( + bucketName, + key, + version, + body, + contentType, + cacheControl, + signal + ) + } + + // Use PutObject directly when content-length is known and within S3's single-object limit (5GB). + // This avoids the buffering overhead of the Upload class which buffers each part in memory. + return this.putObject( + bucketName, + key, + version, + body, + contentType, + cacheControl, + signal, + contentLength + ) + } + + protected async putObject( + bucketName: string, + key: string, + version: string | undefined, + body: Readable, + contentType: string, + cacheControl: string, + signal: AbortSignal | undefined, + contentLength: number + ): Promise { + const dataStream = tracingFeatures?.upload ? monitorStream(body) : body + + const command = new PutObjectCommand({ + Bucket: bucketName, + Key: withOptionalVersion(key, version), + Body: dataStream, + ContentType: contentType, + CacheControl: cacheControl, + ContentLength: contentLength, + }) + + try { + const data = await this.client.send(command, { + abortSignal: signal, + }) + + return { + httpStatusCode: data.$metadata.httpStatusCode || 200, + cacheControl, + eTag: data.ETag || '', + mimetype: contentType, + contentLength, + // PutObject does not return LastModified; keep the fast path single-request and use the local completion time. + lastModified: new Date(), + size: contentLength, + contentRange: undefined, + } + } catch (err) { + if (err instanceof Error && err.name === 'AbortError') { + throw ERRORS.AbortedTerminate('Upload was aborted', err) + } + throw StorageBackendError.fromError(err) + } + } + + protected async bufferedMultipartUpload( + bucketName: string, + key: string, + version: string | undefined, + body: Readable, + contentType: string, + cacheControl: string, + signal?: AbortSignal + ): Promise { const dataStream = tracingFeatures?.upload ? monitorStream(body) : body const upload = new Upload({ client: this.client, + queueSize: storageS3UploadQueueSize, params: { Bucket: bucketName, Key: withOptionalVersion(key, version), @@ -158,24 +251,26 @@ export class S3Backend implements StorageBackendAdapter { signal?.addEventListener('abort', () => upload.abort(), { once: true }) let hasUploadedBytes = false - upload.on('httpUploadProgress', (progress: Progress) => { + const progressHandler = (progress: Progress) => { if (!hasUploadedBytes && progress.loaded && progress.loaded > 0) { hasUploadedBytes = true } if (tracingFeatures?.upload) { dataStream.emit('s3_progress', JSON.stringify(progress)) } - }) + } + upload.on('httpUploadProgress', progressHandler) try { const data = await upload.done() - // Only call head for objects that are > 0 bytes - // for some reason headObject can take a really long time to resolve on zero byte uploads, this was causing requests to timeout - const metadata = hasUploadedBytes + upload.off('httpUploadProgress', progressHandler) + + const metadata: ObjectMetadata = hasUploadedBytes ? await this.headObject(bucketName, key, version) : { httpStatusCode: 200, + cacheControl, eTag: data.ETag || '', mimetype: contentType, lastModified: new Date(), @@ -186,7 +281,7 @@ export class S3Backend implements StorageBackendAdapter { return { httpStatusCode: data.$metadata.httpStatusCode || metadata.httpStatusCode, - cacheControl: cacheControl, + cacheControl, eTag: metadata.eTag, mimetype: metadata.mimetype, contentLength: metadata.contentLength, @@ -195,6 +290,8 @@ export class S3Backend implements StorageBackendAdapter { contentRange: metadata.contentRange, } } catch (err) { + upload.off('httpUploadProgress', progressHandler) + if (err instanceof Error && err.name === 'AbortError') { throw ERRORS.AbortedTerminate('Upload was aborted', err) } @@ -209,23 +306,11 @@ export class S3Backend implements StorageBackendAdapter { * @param version */ async deleteObject(bucket: string, key: string, version: string | undefined): Promise { - try { - const command = new DeleteObjectCommand({ - Bucket: bucket, - Key: withOptionalVersion(key, version), - }) - await this.client.send(command) - } catch (e) { - const err = StorageBackendError.fromError(e) - if (err.code === 'NoSuchKey' || err.error === 'The specified key does not exist.') { - return - } - if (typeof e === 'object' && e !== null && 'name' in e && e.name === 'NoSuchKey') { - return - } - logger.info(`[StorageBackendError] raw: ${JSON.stringify(err)}`) - throw e - } + const command = new DeleteObjectCommand({ + Bucket: bucket, + Key: withOptionalVersion(key, version), + }) + await this.client.send(command) } /** @@ -306,7 +391,8 @@ export class S3Backend implements StorageBackendAdapter { }).map((ele) => { if (options?.prefix) { return { - name: (ele.Key as string).replace(options.prefix, '').replace('/', ''), + // remove prefix and leading slash if present + name: (ele.Key as string).replace(options.prefix, '').replace(/^\//, ''), size: ele.Size as number, } } @@ -329,26 +415,21 @@ export class S3Backend implements StorageBackendAdapter { * @param prefixes */ async deleteObjects(bucket: string, prefixes: string[]): Promise { - await this.runWithConcurrency(prefixes, storageS3DeleteConcurrency, async (key) => { - await this.deleteObject(bucket, key, undefined) - }) - } + try { + const s3Prefixes = prefixes.map((ele) => { + return { Key: ele } + }) - private async runWithConcurrency( - items: T[], - concurrency: number, - worker: (item: T) => Promise - ) { - if (items.length === 0) return - const pool = Math.max(1, concurrency) - let index = 0 - const runners = new Array(Math.min(pool, items.length)).fill(0).map(async () => { - while (index < items.length) { - const i = index++ - await worker(items[i]) - } - }) - await Promise.all(runners) + const command = new DeleteObjectsCommand({ + Bucket: bucket, + Delete: { + Objects: s3Prefixes, + }, + }) + await this.client.send(command) + } catch (e) { + throw StorageBackendError.fromError(e) + } } /** @@ -440,10 +521,12 @@ export class S3Backend implements StorageBackendAdapter { Key: withOptionalVersion(key, version), CacheControl: cacheControl, ContentType: contentType, - Metadata: { - ...metadata, - Version: version || '', - }, + Metadata: metadata + ? { + ...metadata, + Version: version || '', + } + : undefined, }) const resp = await this.client.send(createMultiPart) @@ -540,7 +623,7 @@ export class S3Backend implements StorageBackendAdapter { return { version, - location: location, + location, bucket, ...response, } @@ -594,15 +677,12 @@ export class S3Backend implements StorageBackendAdapter { const params: S3ClientConfig = { region: options.region, runtime: 'node', + requestStreamBufferSize: 32 * 1024, requestHandler: new NodeHttpHandler({ httpAgent: options.httpAgent?.httpAgent, httpsAgent: options.httpAgent?.httpsAgent, - // Reduced connection timeout (3s) to fail fast on network issues - // Prevents queuing during pod startup/shutdown - connectionTimeout: 3000, - // Socket timeout for active requests - increased for large COG files - // but short enough to prevent hanging on stalled connections - requestTimeout: options.requestTimeout || 30000, + connectionTimeout: 5000, + requestTimeout: options.requestTimeout, }), } if (options.endpoint) { @@ -611,6 +691,11 @@ export class S3Backend implements StorageBackendAdapter { if (options.forcePathStyle) { params.forcePathStyle = true } + + if (storageS3DisableChecksum) { + params.requestChecksumCalculation = 'WHEN_REQUIRED' + params.responseChecksumValidation = 'WHEN_REQUIRED' + } return new S3Client(params) } } diff --git a/src/storage/backend/s3/backup.ts b/src/storage/backend/s3/backup.ts index 224f0ffb5..a9c65d6d6 100644 --- a/src/storage/backend/s3/backup.ts +++ b/src/storage/backend/s3/backup.ts @@ -1,11 +1,11 @@ import { - S3Client, + AbortMultipartUploadCommand, + CompletedPart, + CompleteMultipartUploadCommand, CopyObjectCommand, CreateMultipartUploadCommand, + S3Client, UploadPartCopyCommand, - CompleteMultipartUploadCommand, - AbortMultipartUploadCommand, - CompletedPart, } from '@aws-sdk/client-s3' const FIVE_GB = 5 * 1024 * 1024 * 1024 diff --git a/src/storage/cdn/cdn-cache-manager.ts b/src/storage/cdn/cdn-cache-manager.ts index b6f819320..ad82b95b5 100644 --- a/src/storage/cdn/cdn-cache-manager.ts +++ b/src/storage/cdn/cdn-cache-manager.ts @@ -1,7 +1,7 @@ +import { ERRORS } from '@internal/errors' import { Storage } from '@storage/storage' -import axios, { AxiosError } from 'axios' import { HttpsAgent } from 'agentkeepalive' -import { ERRORS } from '@internal/errors' +import axios, { AxiosError } from 'axios' import { getConfig } from '../../config' @@ -16,7 +16,7 @@ const httpsAgent = new HttpsAgent({ const client = axios.create({ baseURL: cdnPurgeEndpointURL, - httpsAgent: httpsAgent, + httpsAgent, headers: { Authorization: `Bearer ${cdnPurgeEndpointKey}`, 'Content-Type': 'application/json', diff --git a/src/storage/database/adapter.ts b/src/storage/database/adapter.ts index c92aae022..d90b0beaf 100644 --- a/src/storage/database/adapter.ts +++ b/src/storage/database/adapter.ts @@ -1,7 +1,7 @@ -import { Bucket, S3MultipartUpload, Obj, S3PartUpload, IcebergCatalog } from '../schemas' -import { ObjectMetadata } from '../backend' import { TenantConnection } from '@internal/database' import { DBMigration } from '@internal/database/migrations' +import { ObjectMetadata } from '../backend' +import { Bucket, IcebergCatalog, Obj, S3MultipartUpload, S3PartUpload } from '../schemas' export interface SearchObjectOption { search?: string @@ -32,6 +32,7 @@ export interface TransactionOptions { isolation?: string retry?: number readOnly?: boolean + timeout?: number } export interface DatabaseOptions { @@ -77,7 +78,7 @@ export interface Database { > ): Promise> - createIcebergBucket(data: Pick): Promise + createAnalyticsBucket(data: Pick): Promise findBucketById( bucketId: string, @@ -87,7 +88,7 @@ export interface Database { countObjectsInBucket(bucketId: string, limit?: number): Promise - deleteBucket(bucketId: string | string[]): Promise + deleteBucket(bucketId: string | string[]): Promise listObjects( bucketId: string, @@ -105,6 +106,11 @@ export interface Database { nextToken?: string maxKeys?: number startAfter?: string + sortBy?: { + order?: string + column?: string + after?: string + } } ): Promise @@ -189,7 +195,8 @@ export interface Database { version: string, signature: string, owner?: string, - metadata?: Record + userMetadata?: Record, + metadata?: Partial ): Promise findMultipartUpload( @@ -213,5 +220,10 @@ export interface Database { options: { afterPart?: string; maxParts: number } ): Promise - deleteAnalyticsBucket(id: string): Promise + deleteAnalyticsBucket(id: string, opts?: { soft: boolean }): Promise + listAnalyticsBuckets( + columns: string, + options: ListBucketOptions | undefined + ): Promise + findAnalyticsBucketByName(name: string): Promise } diff --git a/src/storage/database/knex.test.ts b/src/storage/database/knex.test.ts new file mode 100644 index 000000000..62b3aabfa --- /dev/null +++ b/src/storage/database/knex.test.ts @@ -0,0 +1,9 @@ +import { escapeLike } from './knex' + +describe('escapeLike', () => { + test('escapes SQL wildcard characters', () => { + expect(escapeLike('%_abc')).toBe('\\%\\_abc') + expect(escapeLike('a%b_c')).toBe('a\\%b\\_c') + expect(escapeLike('plain-text')).toBe('plain-text') + }) +}) diff --git a/src/storage/database/knex.ts b/src/storage/database/knex.ts index 9d374cdff..5e50fa510 100644 --- a/src/storage/database/knex.ts +++ b/src/storage/database/knex.ts @@ -1,31 +1,45 @@ -import { Bucket, S3MultipartUpload, Obj, S3PartUpload, IcebergCatalog } from '../schemas' +import { TenantConnection } from '@internal/database' +import { DBMigration, tenantHasMigrations } from '@internal/database/migrations' import { - ErrorCode, ERRORS, + ErrorCode, isStorageError, RenderableError, StorageBackendError, StorageErrorOptions, } from '@internal/errors' -import { ObjectMetadata } from '../backend' +import { hashStringToInt } from '@internal/hashing' +import { dbQueryPerformance } from '@internal/monitoring/metrics' import { Knex } from 'knex' +import { DatabaseError } from 'pg' +import { getConfig } from '../../config' +import { ObjectMetadata } from '../backend' +import { isUuid } from '../limits' +import { Bucket, IcebergCatalog, Obj, S3MultipartUpload, S3PartUpload } from '../schemas' import { Database, DatabaseOptions, FindBucketFilters, FindObjectFilters, - SearchObjectOption, ListBucketOptions, + SearchObjectOption, + TransactionOptions, } from './adapter' -import { DatabaseError } from 'pg' -import { TenantConnection } from '@internal/database' -import { DbQueryPerformance } from '@internal/monitoring/metrics' -import { isUuid } from '../limits' -import { DBMigration, tenantHasMigrations } from '@internal/database/migrations' -import { getConfig } from '../../config' const { isMultitenant } = getConfig() +export function escapeLike(str: string) { + return str.replace(/([%_])/g, '\\$1') +} + +class TestPermissionRollbackError extends Error { + constructor() { + super('Rollback test permission transaction') + this.name = 'TestPermissionRollbackError' + Object.setPrototypeOf(this, TestPermissionRollbackError.prototype) + } +} + /** * Database * the only source of truth for interacting with the storage database @@ -48,9 +62,11 @@ export class StorageKnexDB implements Database { this.latestMigration = options.latestMigration } - //eslint-disable-next-line @typescript-eslint/no-explicit-any - async withTransaction Promise>(fn: T) { - const tnx = await this.connection.transactionProvider(this.options.tnx)() + async withTransaction Promise>( + fn: T, + opts?: TransactionOptions + ) { + const tnx = await this.connection.transactionProvider(this.options.tnx, opts)() try { await this.connection.setScope(tnx) @@ -91,43 +107,115 @@ export class StorageKnexDB implements Database { try { await this.withTransaction(async (db) => { result = await fn(db) - throw true + throw new TestPermissionRollbackError() }) } catch (e) { - if (e === true) { + if (e instanceof TestPermissionRollbackError) { return result } throw e } } - deleteAnalyticsBucket(id: string): Promise { - return this.runQuery('DeleteAnalyticsBucket', async (knex) => { - const deleted = await knex.from('buckets_analytics').where('id', id).delete() + deleteAnalyticsBucket(id: string, opts?: { soft: boolean }): Promise { + return this.runQuery('DeleteAnalyticsBucket', async (knex, signal) => { + if (opts?.soft) { + const softDeleted = await knex + .from('buckets_analytics') + .where('id', id) + .whereNull('deleted_at') + .update({ deleted_at: new Date() }) + .returning('*') + .abortOnSignal(signal) + + if (softDeleted.length === 0) { + throw ERRORS.NoSuchBucket(id) + } + + return softDeleted[0] + } - if (deleted === 0) { + const deleted = await knex + .from('buckets_analytics') + .where('id', id) + .delete() + .returning('*') + .abortOnSignal(signal) + + if (deleted.length === 0) { throw ERRORS.NoSuchBucket(id) } + + return deleted[0] }) } - createIcebergBucket(data: Pick): Promise { - const bucketData: IcebergCatalog = { - id: data.id, + listAnalyticsBuckets( + columns: string, + options: ListBucketOptions | undefined + ): Promise { + return this.runQuery('ListIcebergBuckets', async (knex, signal) => { + const query = knex + .from('buckets_analytics') + .select(columns.split(',').map((c) => c.trim())) + .whereNull('deleted_at') + + if (options?.search !== undefined && options.search.length > 0) { + query.where('name', 'like', `%${escapeLike(options.search)}%`) + } + + if (options?.sortColumn !== undefined) { + query.orderBy(options.sortColumn, options.sortOrder || 'asc') + } else { + query.orderBy('name', 'asc') + } + + if (options?.limit !== undefined) { + query.limit(options.limit) + } + + if (options?.offset !== undefined) { + query.offset(options.offset) + } + + return query.abortOnSignal(signal) + }) + } + + findAnalyticsBucketByName(name: string) { + return this.runQuery('FindAnalyticsBucketByName', async (knex, signal) => { + const icebergBucket = await knex + .from('buckets_analytics') + .select('*') + .where('name', name) + .whereNull('deleted_at') + .first() + .abortOnSignal(signal) + + if (!icebergBucket) { + throw ERRORS.NoSuchBucket(name) + } + + return icebergBucket + }) + } + + createAnalyticsBucket(data: Pick): Promise { + const bucketData: Pick = { + name: data.name, } - return this.runQuery('CreateAnalyticsBucket', async (knex) => { + return this.runQuery('CreateAnalyticsBucket', async (knex, signal) => { const icebergBucket = await knex .from('buckets_analytics') .insert(bucketData) - .onConflict('id') - .merge({ - updated_at: new Date().toISOString(), - }) + .onConflict(knex.raw('(name) WHERE deleted_at IS NULL')) + .ignore() .returning('*') + .abortOnSignal(signal) if (icebergBucket.length === 0) { - throw ERRORS.NoSuchBucket(data.id) + throw ERRORS.ResourceAlreadyExists() } return icebergBucket[0] @@ -155,11 +243,11 @@ export class StorageKnexDB implements Database { } try { - const bucket = await this.runQuery('CreateBucket', async (knex) => { - return knex.from('buckets').insert(bucketData) as Promise<{ rowCount: number }> + const rowCount = await this.runQuery('CreateBucket', async (knex, signal) => { + return knex.from('buckets').insert(bucketData).abortOnSignal(signal) }) - if (bucket.rowCount === 0) { + if (!rowCount || rowCount[0] === 0) { throw ERRORS.NoSuchBucket(data.id) } @@ -173,7 +261,7 @@ export class StorageKnexDB implements Database { } async findBucketById(bucketId: string, columns = 'id', filters?: FindBucketFilters) { - const result = await this.runQuery('FindBucketById', async (knex) => { + const result = await this.runQuery('FindBucketById', async (knex, signal) => { let columnNames = columns.split(',') if (!(await tenantHasMigrations(this.tenantId, 'iceberg-catalog-flag-on-buckets'))) { @@ -196,7 +284,7 @@ export class StorageKnexDB implements Database { query.forShare() } - return query.first() as Promise + return query.abortOnSignal(signal).first() as Promise }) if (!result && !filters?.dontErrorOnEmpty) { @@ -209,25 +297,36 @@ export class StorageKnexDB implements Database { async countObjectsInBucket(bucketId: string, limit?: number): Promise { // if we have a limit use select to only scan up to that limit if (limit !== undefined) { - const result = await this.runQuery('CountObjectsInBucketWithLimit', (knex) => { - return knex.from('objects').where('bucket_id', bucketId).limit(limit).select(knex.raw('1')) + const result = await this.runQuery('CountObjectsInBucketWithLimit', (knex, signal) => { + return knex + .from('objects') + .where('bucket_id', bucketId) + .limit(limit) + .select(knex.raw('1')) + .abortOnSignal(signal) }) return result.length } // do full count if there is no limit - const result = await this.runQuery('CountObjectsInBucket', (knex) => { - return knex.from('objects').where('bucket_id', bucketId).count().first<{ count: number }>() + const result = await this.runQuery('CountObjectsInBucket', (knex, signal) => { + return knex + .from('objects') + .where('bucket_id', bucketId) + .count() + .abortOnSignal(signal) + .first<{ count: number }>() }) return result?.count || 0 } async deleteBucket(bucketId: string | string[]) { - return await this.runQuery('DeleteBucket', (knex) => { + return await this.runQuery('DeleteBucket', (knex, signal) => { return knex('buckets') .whereIn('id', Array.isArray(bucketId) ? bucketId : [bucketId]) .delete() + .abortOnSignal(signal) }) } @@ -238,7 +337,7 @@ export class StorageKnexDB implements Database { before?: Date, nextToken?: string ) { - const data = await this.runQuery('ListObjects', (knex) => { + const data = await this.runQuery('ListObjects', (knex, signal) => { const query = knex .from('objects') .select(columns.split(',')) @@ -255,7 +354,7 @@ export class StorageKnexDB implements Database { query.andWhere(knex.raw('name COLLATE "C" > ?', [nextToken])) } - return query as Promise + return query.abortOnSignal(signal) as Promise }) return data @@ -269,30 +368,63 @@ export class StorageKnexDB implements Database { nextToken?: string maxKeys?: number startAfter?: string + sortBy?: { + order?: string + column?: string + after?: string + } } ) { - return this.runQuery('ListObjectsV2', async (knex) => { + return this.runQuery('ListObjectsV2', async (knex, signal) => { if (!options?.delimiter) { const query = knex .table('objects') .where('bucket_id', bucketId) - .select(['id', 'name', 'metadata', 'updated_at']) + .select(['id', 'name', 'metadata', 'updated_at', 'created_at', 'last_accessed_at']) .limit(options?.maxKeys || 100) + // only allow these values for sort columns, "name" is excluded intentionally as it is the default and used as tie breaker when sorting by other columns + const allowedSortColumns = new Set(['updated_at', 'created_at']) + const allowedSortOrders = new Set(['asc', 'desc']) + const sortColumn = + options?.sortBy?.column && allowedSortColumns.has(options.sortBy.column) + ? options.sortBy.column + : undefined + const sortOrder = + options?.sortBy?.order && allowedSortOrders.has(options.sortBy.order) + ? options.sortBy.order + : 'asc' + + if (sortColumn) { + query.orderBy(sortColumn, sortOrder) + } // knex typing is wrong, it doesn't accept a knex.raw on orderBy, even though is totally legit - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - query.orderBy(knex.raw('name COLLATE "C"')) + // @ts-expect-error + query.orderBy(knex.raw(`name COLLATE "C"`), sortOrder) if (options?.prefix) { - query.where('name', 'like', `${options.prefix}%`) + query.where('name', 'like', `${escapeLike(options.prefix)}%`) + } + + if (options?.startAfter && !options?.nextToken) { + query.andWhere(knex.raw(`name COLLATE "C" > ?`, [options.startAfter])) } if (options?.nextToken) { - query.andWhere(knex.raw('name COLLATE "C" > ?', [options?.nextToken])) + const pageOperator = sortOrder === 'asc' ? '>' : '<' + if (sortColumn && options.sortBy?.after) { + query.andWhere( + knex.raw( + `ROW(date_trunc('milliseconds', ${sortColumn}), name COLLATE "C") ${pageOperator} ROW(COALESCE(NULLIF(?, '')::timestamptz, 'epoch'::timestamptz), ?)`, + [options.sortBy.after, options.nextToken] + ) + ) + } else { + query.andWhere(knex.raw(`name COLLATE "C" ${pageOperator} ?`, [options.nextToken])) + } } - return query + return query.abortOnSignal(signal) } let useNewSearchVersion2 = true @@ -302,112 +434,66 @@ export class StorageKnexDB implements Database { } if (useNewSearchVersion2 && options?.delimiter === '/') { + let paramPlaceholders = '?,?,?,?,?' + const sortParams: (string | null)[] = [] + // this migration adds 3 more parameters to search v2 support sorting + // 'search-v2-optimised' also implies sort support (it's a newer migration) + const hasSortSupport = + (await tenantHasMigrations(this.tenantId, 'add-search-v2-sort-support')) || + (await tenantHasMigrations(this.tenantId, 'search-v2-optimised')) + if (hasSortSupport) { + paramPlaceholders += ',?,?,?' + sortParams.push( + options?.sortBy?.order || 'asc', + options?.sortBy?.column || 'name', + options?.sortBy?.after || null + ) + } const levels = !options?.prefix ? 1 : options.prefix.split('/').length - const query = await knex.raw('select * from storage.search_v2(?,?,?,?,?)', [ + const searchParams = [ options?.prefix || '', bucketId, options?.maxKeys || 1000, levels, options?.startAfter || '', - ]) - - return query.rows + ...sortParams, + ] + const result = await knex + .raw(`select * from storage.search_v2(${paramPlaceholders})`, searchParams) + .abortOnSignal(signal) + return result.rows } - const query = await knex.raw( - 'select * from storage.list_objects_with_delimiter(?,?,?,?,?,?)', - [ + const result = await knex + .raw('select * from storage.list_objects_with_delimiter(?,?,?,?,?,?)', [ bucketId, options?.prefix, options?.delimiter, options?.maxKeys, options?.startAfter || '', options?.nextToken || '', - ] - ) - - return query.rows + ]) + .abortOnSignal(signal) + return result.rows }) } - async listAllBucketTypes(columns = 'id', options?: ListBucketOptions) { - const data = await this.runQuery('ListAllBucketTypes', async (knex) => { - // 1) figure out which columns we’re selecting + async listBuckets(columns = 'id', options?: ListBucketOptions) { + const data = await this.runQuery('ListBuckets', async (knex, signal) => { const columnNames = columns.split(',').map((c) => c.trim()) - // 2) build the two “source” queries - const bucketQ = knex - .select(columnNames) - .from('buckets') - .modify((qb) => { - if (options?.search) { - qb.where('name', 'ilike', `%${options.search}%`) - } - }) - - const icebergBucketsAllowedColumnNames = ['id', 'type', 'created_at', 'updated_at'] - - const icebergBucketsColumns = columnNames.map((name) => { - if (name === 'name') { - return 'id as name' - } - if (!icebergBucketsAllowedColumnNames.includes(name)) { - return knex.raw('null as ??', [name]) - } - return name + const selectColumns = columnNames.filter((name) => { + return name !== 'type' }) - const icebergQ = knex - .select(icebergBucketsColumns) - .from('buckets_analytics') - .modify((qb) => { - // if you want to search iceberg buckets by their id: - if (options?.search) { - qb.where('id', 'ilike', `%${options.search}%`) - } - }) - - // 3) union them together, wrap as a sub‐query - const combined = knex.unionAll([bucketQ, icebergQ], /* wrapParens=*/ true).as('all_buckets') - - // 4) now select * from that union, then sort / page - const finalQ = knex - .select('*') - .from(combined) - .modify((qb) => { - if (options?.sortColumn) { - qb.orderBy(options.sortColumn, options.sortOrder || 'asc') - } - if (options?.limit !== undefined) { - qb.limit(options.limit) - } - if (options?.offset !== undefined) { - qb.offset(options.offset) - } - }) - - return finalQ - }) - - return data as Bucket[] - } - - async listBuckets(columns = 'id', options?: ListBucketOptions) { - if (await tenantHasMigrations(this.tenantId, 'iceberg-catalog-flag-on-buckets')) { - return this.listAllBucketTypes(columns, options) - } - - const data = await this.runQuery('ListBuckets', async (knex) => { - let columnNames = columns.split(',') - - columnNames = columnNames.filter((name) => { - return name.trim() !== 'type' - }) + if (columnNames.includes('type')) { + selectColumns.push(knex.raw("'STANDARD' as type") as unknown as string) + } - const query = knex.from('buckets').select(columnNames) + const query = knex.from('buckets').select(selectColumns) if (options?.search !== undefined && options.search.length > 0) { - query.where('name', 'ilike', `%${options.search}%`) + query.where('name', 'ilike', `%${escapeLike(options.search)}%`) } if (options?.sortColumn !== undefined) { @@ -422,7 +508,7 @@ export class StorageKnexDB implements Database { query.offset(options.offset) } - return query + return query.abortOnSignal(signal) }) return data as Bucket[] @@ -438,7 +524,7 @@ export class StorageKnexDB implements Database { maxKeys?: number } ) { - return this.runQuery('ListMultipartsUploads', async (knex) => { + return this.runQuery('ListMultipartsUploads', async (knex, signal) => { if (!options?.deltimeter) { const query = knex .table('s3_multipart_uploads') @@ -447,12 +533,11 @@ export class StorageKnexDB implements Database { .limit(options?.maxKeys || 100) // knex typing is wrong, it doesn't accept a knex.raw on orderBy, even though is totally legit - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore + // @ts-expect-error query.orderBy(knex.raw('key COLLATE "C", created_at')) if (options?.prefix) { - query.where('key', 'ilike', `${options.prefix}%`) + query.where('key', 'ilike', `${escapeLike(options.prefix)}%`) } if (options?.nextUploadKeyToken && !options.nextUploadToken) { @@ -463,22 +548,20 @@ export class StorageKnexDB implements Database { query.andWhere(knex.raw('id COLLATE "C" > ?', [options?.nextUploadToken])) } - return query + return query.abortOnSignal(signal) } - const query = await knex.raw( - 'select * from storage.list_multipart_uploads_with_delimiter(?,?,?,?,?,?)', - [ + const result = await knex + .raw('select * from storage.list_multipart_uploads_with_delimiter(?,?,?,?,?,?)', [ bucketId, - options?.prefix, + options?.prefix ? escapeLike(options.prefix) : options?.prefix, options?.deltimeter, options?.maxKeys, options?.nextUploadKeyToken || '', options.nextUploadToken || '', - ] - ) - - return query.rows + ]) + .abortOnSignal(signal) + return result.rows }) } @@ -486,12 +569,16 @@ export class StorageKnexDB implements Database { bucketId: string, fields: Pick ) { - const bucket = await this.runQuery('UpdateBucket', (knex) => { - return knex.from('buckets').where('id', bucketId).update({ - public: fields.public, - file_size_limit: fields.file_size_limit, - allowed_mime_types: fields.allowed_mime_types, - }) + const bucket = await this.runQuery('UpdateBucket', (knex, signal) => { + return knex + .from('buckets') + .where('id', bucketId) + .update({ + public: fields.public, + file_size_limit: fields.file_size_limit, + allowed_mime_types: fields.allowed_mime_types, + }) + .abortOnSignal(signal) }) if (bucket === 0) { @@ -513,7 +600,7 @@ export class StorageKnexDB implements Database { user_metadata: data.user_metadata, version: data.version, }) - const [object] = await this.runQuery('UpsertObject', (knex) => { + const [object] = await this.runQuery('UpsertObject', (knex, signal) => { return knex .from('objects') .insert(objectData) @@ -528,6 +615,7 @@ export class StorageKnexDB implements Database { }) ) .returning('*') + .abortOnSignal(signal) }) return object @@ -538,7 +626,7 @@ export class StorageKnexDB implements Database { name: string, data: Pick ) { - const [object] = await this.runQuery('UpdateObject', (knex) => { + const [object] = await this.runQuery('UpdateObject', (knex, signal) => { return knex .from('objects') .where('bucket_id', bucketId) @@ -555,6 +643,7 @@ export class StorageKnexDB implements Database { }), '*' ) + .abortOnSignal(signal) }) if (!object) { @@ -577,8 +666,8 @@ export class StorageKnexDB implements Database { version: data.version, user_metadata: data.user_metadata, }) - await this.runQuery('CreateObject', (knex) => { - return knex.from('objects').insert(object) + await this.runQuery('CreateObject', (knex, signal) => { + return knex.from('objects').insert(object).abortOnSignal(signal) }) return object @@ -591,7 +680,7 @@ export class StorageKnexDB implements Database { } async deleteObject(bucketId: string, objectName: string, version?: string) { - const [data] = await this.runQuery('Delete Object', (knex) => { + const [data] = await this.runQuery('Delete Object', (knex, signal) => { return knex .from('objects') .delete() @@ -601,26 +690,26 @@ export class StorageKnexDB implements Database { ...(version ? { version } : {}), }) .returning('*') + .abortOnSignal(signal) }) return data } async deleteObjects(bucketId: string, objectNames: string[], by: keyof Obj = 'name') { - const objects = await this.runQuery('DeleteObjects', (knex) => { + return this.runQuery('DeleteObjects', (knex, signal) => { return knex .from('objects') .delete() .where('bucket_id', bucketId) .whereIn(by, objectNames) .returning('*') + .abortOnSignal(signal) }) - - return objects } async deleteObjectVersions(bucketId: string, objectNames: { name: string; version: string }[]) { - const objects = await this.runQuery('DeleteObjects', (knex) => { + return this.runQuery('DeleteObjects', (knex, signal) => { const placeholders = objectNames.map(() => '(?, ?)').join(', ') // Step 2: Flatten the array of tuples into a single array of values @@ -632,27 +721,25 @@ export class StorageKnexDB implements Database { .where('bucket_id', bucketId) .whereRaw(`(name, version) IN (${placeholders})`, flatParams) .returning('*') + .abortOnSignal(signal) }) - - return objects } async updateObjectMetadata(bucketId: string, objectName: string, metadata: ObjectMetadata) { - const [object] = await this.runQuery('UpdateObjectMetadata', (knex) => { + const [object] = await this.runQuery('UpdateObjectMetadata', (knex, signal) => { return knex .from('objects') - .update({ - metadata, - }) + .update({ metadata }) .where({ bucket_id: bucketId, name: objectName }) .returning('*') + .abortOnSignal(signal) }) return object } async updateObjectOwner(bucketId: string, objectName: string, owner?: string) { - const [object] = await this.runQuery('UpdateObjectOwner', (knex) => { + const [object] = await this.runQuery('UpdateObjectOwner', (knex, signal) => { return knex .from('objects') .update({ @@ -662,6 +749,7 @@ export class StorageKnexDB implements Database { }) .returning('*') .where({ bucket_id: bucketId, name: objectName }) + .abortOnSignal(signal) }) if (!object) { @@ -677,7 +765,7 @@ export class StorageKnexDB implements Database { columns = 'id', filters?: FindObjectFilters ) { - const object = await this.runQuery('FindObject', (knex) => { + const object = await this.runQuery('FindObject', (knex, signal) => { const query = knex .from('objects') .select(this.normalizeColumns(columns).split(',')) @@ -702,7 +790,7 @@ export class StorageKnexDB implements Database { query.noWait() } - return query.first() as Promise + return query.abortOnSignal(signal).first() as Promise }) if (!object && !filters?.dontErrorOnEmpty) { @@ -717,19 +805,18 @@ export class StorageKnexDB implements Database { } async findObjects(bucketId: string, objectNames: string[], columns = 'id') { - const objects = await this.runQuery('FindObjects', (knex) => { + return this.runQuery('FindObjects', (knex, signal) => { return knex .from('objects') .select(columns) .where('bucket_id', bucketId) .whereIn('name', objectNames) + .abortOnSignal(signal) }) - - return objects } async findObjectVersions(bucketId: string, obj: { name: string; version: string }[]) { - const objects = await this.runQuery('FindObjectVersions', (knex) => { + return this.runQuery('FindObjectVersions', (knex, signal) => { // Step 1: Generate placeholders for each tuple const placeholders = obj.map(() => '(?, ?)').join(', ') @@ -741,18 +828,19 @@ export class StorageKnexDB implements Database { .select('objects.name', 'objects.version') .where('bucket_id', bucketId) .whereRaw(`(name, version) IN (${placeholders})`, flatParams) + .abortOnSignal(signal) }) - - return objects } async mustLockObject(bucketId: string, objectName: string, version?: string) { - return this.runQuery('MustLockObject', async (knex) => { + return this.runQuery('MustLockObject', async (knex, signal) => { const hash = hashStringToInt(`${bucketId}/${objectName}${version ? `/${version}` : ''}`) - const result = await knex.raw<{ rows: { pg_try_advisory_xact_lock: boolean }[] }>( - `SELECT pg_try_advisory_xact_lock(?);`, - [hash] - ) + const result = await knex + .raw<{ rows: { pg_try_advisory_xact_lock: boolean }[] }>( + `SELECT pg_try_advisory_xact_lock(?);`, + [hash] + ) + .abortOnSignal(signal) const lockAcquired = result.rows.shift()?.pg_try_advisory_xact_lock || false if (!lockAcquired) { @@ -769,9 +857,9 @@ export class StorageKnexDB implements Database { version?: string, opts?: { timeout: number } ) { - return this.runQuery('WaitObjectLock', async (knex) => { + return this.runQuery('WaitObjectLock', async (knex, signal) => { const hash = hashStringToInt(`${bucketId}/${objectName}${version ? `/${version}` : ''}`) - const query = knex.raw(`SELECT pg_advisory_xact_lock(?)`, [hash]) + const query = knex.raw(`SELECT pg_advisory_xact_lock(?)`, [hash]).abortOnSignal(signal) if (opts?.timeout) { let timeoutInterval: undefined | NodeJS.Timeout @@ -800,20 +888,26 @@ export class StorageKnexDB implements Database { } async searchObjects(bucketId: string, prefix: string, options: SearchObjectOption) { - return this.runQuery('SearchObjects', async (knex) => { - const result = await knex.raw<{ rows: Obj[] }>( - 'select * from storage.search(?,?,?,?,?,?,?,?)', - [ - prefix, + return this.runQuery('SearchObjects', async (knex, signal) => { + const sortColumn = options.sortBy?.column ?? 'name' + const shouldEscapePattern = sortColumn !== 'name' + const safePrefix = shouldEscapePattern ? escapeLike(prefix) : prefix + const safeSearch = shouldEscapePattern + ? escapeLike(options.search || '') + : options.search || '' + + const result = await knex + .raw<{ rows: Obj[] }>('select * from storage.search(?,?,?,?,?,?,?,?)', [ + safePrefix, bucketId, options.limit || 100, - prefix.split('/').length, + safePrefix.split('/').length, options.offset || 0, - options.search || '', - options.sortBy?.column ?? 'name', + safeSearch, + sortColumn, options.sortBy?.order ?? 'asc', - ] - ) + ]) + .abortOnSignal(signal) return result.rows }) @@ -826,39 +920,59 @@ export class StorageKnexDB implements Database { version: string, signature: string, owner?: string, - metadata?: Record + userMetadata?: Record, + metadata?: Partial ) { - return this.runQuery('CreateMultipartUpload', async (knex) => { + return this.runQuery('CreateMultipartUpload', async (knex, signal) => { + const data: Record = { + id: uploadId, + bucket_id: bucketId, + key: objectName, + version, + upload_signature: signature, + owner_id: owner, + user_metadata: userMetadata, + } + + // TODO: move this guard into normalizeColumns once it is table-aware. + // metadata was added to s3_multipart_uploads in migration 57 but has existed on + // objects since much earlier, so a table-agnostic rule would incorrectly strip it. + if ( + !this.latestMigration || + DBMigration[this.latestMigration] >= DBMigration['s3-multipart-uploads-metadata'] + ) { + data.metadata = metadata + } + const multipart = await knex .table('s3_multipart_uploads') - .insert( - this.normalizeColumns({ - id: uploadId, - bucket_id: bucketId, - key: objectName, - version, - upload_signature: signature, - owner_id: owner, - user_metadata: metadata, - }) - ) + .insert(this.normalizeColumns(data)) .returning('*') + .abortOnSignal(signal) return multipart[0] as S3MultipartUpload }) } async findMultipartUpload(uploadId: string, columns = 'id', options?: { forUpdate?: boolean }) { - const multiPart = await this.runQuery('FindMultipartUpload', async (knex) => { - const query = knex - .from('s3_multipart_uploads') - .select(columns.split(',')) - .where('id', uploadId) + const multiPart = await this.runQuery('FindMultipartUpload', async (knex, signal) => { + // TODO: move this guard into normalizeColumns once it is table-aware. + // metadata was added to s3_multipart_uploads in migration 57 but has existed on + // objects since much earlier, so a table-agnostic rule would incorrectly strip it. + const hasMetadataColumn = + !this.latestMigration || + DBMigration[this.latestMigration] >= DBMigration['s3-multipart-uploads-metadata'] + + const cols = hasMetadataColumn + ? columns.split(',') + : columns.split(',').filter((col) => col.trim() !== 'metadata') + + const query = knex.from('s3_multipart_uploads').select(cols).where('id', uploadId) if (options?.forUpdate) { - return query.forUpdate().first() + return query.abortOnSignal(signal).forUpdate().first() } - return query.first() + return query.abortOnSignal(signal).first() }) if (!multiPart) { @@ -868,26 +982,28 @@ export class StorageKnexDB implements Database { } async updateMultipartUploadProgress(uploadId: string, progress: number, signature: string) { - return this.runQuery('UpdateMultipartUploadProgress', async (knex) => { + return this.runQuery('UpdateMultipartUploadProgress', async (knex, signal) => { await knex .from('s3_multipart_uploads') .update({ in_progress_size: progress, upload_signature: signature }) .where('id', uploadId) + .abortOnSignal(signal) }) } async deleteMultipartUpload(uploadId: string) { - return this.runQuery('DeleteMultipartUpload', async (knex) => { - await knex.from('s3_multipart_uploads').delete().where('id', uploadId) + return this.runQuery('DeleteMultipartUpload', async (knex, signal) => { + await knex.from('s3_multipart_uploads').delete().where('id', uploadId).abortOnSignal(signal) }) } async insertUploadPart(part: S3PartUpload) { - return this.runQuery('InsertUploadPart', async (knex) => { + return this.runQuery('InsertUploadPart', async (knex, signal) => { const storedPart = await knex .table('s3_multipart_uploads_parts') .insert(part) .returning('*') + .abortOnSignal(signal) return storedPart[0] }) @@ -897,7 +1013,7 @@ export class StorageKnexDB implements Database { uploadId: string, options: { afterPart?: string; maxParts: number } ): Promise { - return this.runQuery('ListParts', async (knex) => { + return this.runQuery('ListParts', async (knex, signal) => { const query = knex .from('s3_multipart_uploads_parts') .select('etag', 'part_number', 'size', 'upload_id', 'created_at') @@ -909,13 +1025,13 @@ export class StorageKnexDB implements Database { query.andWhere('part_number', '>', options.afterPart) } - return query + return query.abortOnSignal(signal) }) } healthcheck() { - return this.runQuery('Healthcheck', (knex) => { - return knex.raw('SELECT id from storage.buckets limit 1') + return this.runQuery('Healthcheck', (knex, signal) => { + return knex.raw('SELECT id from storage.buckets limit 1').abortOnSignal(signal) }) } @@ -959,13 +1075,19 @@ export class StorageKnexDB implements Database { return columns } - protected async runQuery Promise>( - queryName: string, - fn: T - ): Promise>> { - const timer = DbQueryPerformance.startTimer({ - name: queryName, - }) + protected async runQuery< + T extends (...args: [db: Knex.Transaction, signal?: AbortSignal]) => Promise, + >(queryName: string, fn: T): Promise>> { + const startTime = process.hrtime.bigint() + const recordDuration = () => { + const duration = Number(process.hrtime.bigint() - startTime) / 1e9 + dbQueryPerformance.record(duration, { + name: queryName, + tenantId: this.tenantId, + }) + } + + const abortSignal = this.connection.getAbortSignal() let tnx = this.options.tnx @@ -987,7 +1109,7 @@ export class StorageKnexDB implements Database { await this.connection.setScope(tnx) } - const result: Awaited> = await fn(tnx) + const result: Awaited> = await fn(tnx, abortSignal) if (needsNewTransaction) { await tnx.commit() @@ -999,14 +1121,14 @@ export class StorageKnexDB implements Database { } } - timer() + recordDuration() return result } catch (e) { if (needsNewTransaction) { await tnx.rollback() } - timer() + recordDuration() throw e } } @@ -1044,21 +1166,44 @@ export class DBError extends StorageBackendError implements RenderableError { query, code: pgError.code, }) + case '57014': // query_canceled (statement_timeout or user cancel) + return ERRORS.DatabaseTimeout(pgError).withMetadata({ + query, + code: pgError.code, + }) + case '25006': // read only sql transaction + return ERRORS.DatabaseReadOnly(pgError).withMetadata({ + query, + code: pgError.code, + }) + case '42P17': // invalid object definition + return ERRORS.InvalidObjectDefinition(pgError).withMetadata({ + query, + code: pgError.code, + }) + case '22P02': // invalid text representation + return ERRORS.InvalidParameter('value', { + error: pgError, + message: pgError.message || 'Invalid value format or type conversion failed', + }).withMetadata({ + query, + code: pgError.code, + }) + case '42703': // undefined column + return ERRORS.DatabaseSchemaMismatch(pgError).withMetadata({ + query, + code: pgError.code, + }) + case '42P01': // relation does not exist + return ERRORS.DatabaseSchemaMismatch(pgError).withMetadata({ + query, + code: pgError.code, + }) default: - return ERRORS.DatabaseError(pgError.message, pgError).withMetadata({ + return ERRORS.DatabaseError(`database error, code: ${pgError.code}`, pgError).withMetadata({ query, code: pgError.code, }) } } } - -export default function hashStringToInt(str: string): number { - let hash = 5381 - let i = -1 - while (i < str.length - 1) { - i += 1 - hash = (hash * 33) ^ str.charCodeAt(i) - } - return hash >>> 0 -} diff --git a/src/storage/events/base-event.ts b/src/storage/events/base-event.ts index 0163eceb9..40f8bc30a 100644 --- a/src/storage/events/base-event.ts +++ b/src/storage/events/base-event.ts @@ -1,12 +1,12 @@ -import { Event as QueueBaseEvent, BasePayload, StaticThis, Event } from '@internal/queue' import { getPostgresConnection, getServiceKeyUser } from '@internal/database' -import { StorageKnexDB } from '../database' -import { createStorageBackend, StorageBackendAdapter } from '../backend' -import { Storage } from '../storage' -import { getConfig } from '../../config' -import { logger } from '@internal/monitoring' import { createAgent } from '@internal/http' +import { logger } from '@internal/monitoring' +import { BasePayload, Event, Event as QueueBaseEvent, StaticThis } from '@internal/queue' import { TenantLocation } from '@storage/locator' +import { getConfig } from '../../config' +import { createStorageBackend, StorageBackendAdapter } from '../backend' +import { StorageKnexDB } from '../database' +import { Storage } from '../storage' const { storageS3Bucket, storageS3MaxSockets, storageBackendType, region } = getConfig() @@ -29,8 +29,7 @@ export abstract class BaseEvent> extends this: StaticThis, payload: Omit ) { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { Webhook } = require('./lifecycle/webhook') + const { Webhook } = await import('./lifecycle/webhook') const eventType = this.eventName() try { @@ -90,7 +89,7 @@ export abstract class BaseEvent> extends }) storageBackend = createStorageBackend(storageBackendType, { - httpAgent: httpAgent, + httpAgent, }) if (monitor) { diff --git a/src/storage/events/iceberg/delete-iceberg-resources.ts b/src/storage/events/iceberg/delete-iceberg-resources.ts new file mode 100644 index 000000000..b6923a289 --- /dev/null +++ b/src/storage/events/iceberg/delete-iceberg-resources.ts @@ -0,0 +1,167 @@ +import { multitenantKnex } from '@internal/database' +import { ERRORS } from '@internal/errors' +import { BasePayload } from '@internal/queue' +import { KnexShardStoreFactory, ShardCatalog } from '@internal/sharding' +import { getCatalogAuthStrategy, RestCatalogClient } from '@storage/protocols/iceberg/catalog' +import { IcebergError } from '@storage/protocols/iceberg/catalog/errors' +import { KnexMetastore } from '@storage/protocols/iceberg/knex' +import { Job, Queue as PgBossQueue, SendOptions, WorkOptions } from 'pg-boss' +import { getConfig } from '../../../config' +import { BaseEvent } from '../base-event' + +const { icebergCatalogUrl, icebergCatalogAuthType, isMultitenant } = getConfig() + +const catalogAuthType = getCatalogAuthStrategy(icebergCatalogAuthType) + +interface DeleteIcebergResourcesPayload extends BasePayload { + catalogId: string +} + +export class DeleteIcebergResources extends BaseEvent { + static allowSync = false + static queueName = 'delete-iceberg-resources' + + static getQueueOptions(): PgBossQueue { + return { + name: this.queueName, + policy: 'exactly_once', + } as const + } + + static getWorkerOptions(): WorkOptions { + return { + includeMetadata: true, + } + } + + static getSendOptions(payload: DeleteIcebergResourcesPayload): SendOptions { + return { + expireInHours: 2, + singletonKey: payload.catalogId, + expireInMinutes: 120, + singletonHours: 12, + retryLimit: 3, + retryDelay: 5, + priority: 10, + } + } + + static async handle(job: Job) { + const storage = await this.createStorage(job.data) + const db = isMultitenant ? multitenantKnex : storage.db.connection.pool.acquire() + + const metastore = new KnexMetastore(db, { + multiTenant: isMultitenant, + schema: isMultitenant ? 'public' : 'storage', + }) + + const restCatalog = new RestCatalogClient({ + catalogUrl: icebergCatalogUrl, + auth: catalogAuthType, + }) + + await metastore.transaction(async (store) => { + await store.lockResource('catalog', job.data.catalogId) + + const catalog = await store.findCatalogById({ + id: job.data.catalogId, + deleted: true, + tenantId: job.data.tenant.ref, + }) + + if (catalog.deleted_at) { + throw ERRORS.UnableToEmptyBucket( + job.data.catalogId, + `Catalog ${job.data.catalogId} is already being deleted` + ) + } + + const namespaces = await store.listNamespaces({ + catalogId: job.data.catalogId, + tenantId: job.data.tenant.ref, + }) + + // Delete all tables and namespaces in the catalog + await Promise.all( + namespaces.map(async (ns) => { + const tables = await store.listTables({ + namespaceId: ns.id, + pageSize: 1000, + tenantId: job.data.tenant.ref, + }) + + for (const table of tables) { + if (!table.shard_key || !table.shard_id) { + continue + } + + try { + await restCatalog.dropTable({ + namespace: ns.name, + table: table.name, + purgeRequested: true, + warehouse: table.shard_key, + }) + } catch (e) { + if (e instanceof IcebergError && e.code === 404) { + // Table not found in remote catalog, continue to delete metadata + } else { + throw e + } + } + + await store.dropTable({ + name: table.name, + namespaceId: ns.id, // namespace_id UUID + catalogId: job.data.catalogId, + tenantId: job.data.tenant.ref, + }) + + const listTables = await restCatalog.listTables({ + namespace: `${job.data.tenant.ref}_${ns.id.replaceAll('-', '_')}`, + warehouse: table.shard_key, + pageSize: 1, + }) + + if (listTables.identifiers.length === 0) { + await restCatalog.dropNamespace({ + namespace: ns.name, + warehouse: table.shard_key, + }) + // Delete the namespace metadata after removing it from remote catalog + await store.dropNamespace({ + namespace: ns.name, + catalogId: job.data.catalogId, + tenantId: job.data.tenant.ref, + }) + } + + if (isMultitenant) { + const sharding = new ShardCatalog(new KnexShardStoreFactory(multitenantKnex)) + const sharder = sharding.withTnx(store.getTnx()) + await sharder.freeByResource(table.shard_id, { + kind: 'iceberg-table', + tenantId: job.data.tenant.ref, + bucketName: job.data.catalogId, + logicalName: `${ns.id}/${table.name}`, + }) + } + } + }) + ) + + // Finally, drop the catalog + // Child rows are already deleted, so this won't trigger cascading deletes + await store.dropCatalog({ + bucketId: job.data.catalogId, + tenantId: job.data.tenant.ref, + soft: false, + }) + + if (isMultitenant) { + // Delete the underlying bucket + await storage.db.deleteAnalyticsBucket(job.data.catalogId) + } + }) + } +} diff --git a/src/storage/events/iceberg/index.ts b/src/storage/events/iceberg/index.ts new file mode 100644 index 000000000..98812fd37 --- /dev/null +++ b/src/storage/events/iceberg/index.ts @@ -0,0 +1,3 @@ +export * from '../upgrades/sync-catalog-ids' +export * from './delete-iceberg-resources' +export * from './reconcile-catalog' diff --git a/src/storage/events/iceberg/reconcile-catalog.ts b/src/storage/events/iceberg/reconcile-catalog.ts new file mode 100644 index 000000000..6c2d66295 --- /dev/null +++ b/src/storage/events/iceberg/reconcile-catalog.ts @@ -0,0 +1,54 @@ +import { BasePayload } from '@internal/queue' +import { + getCatalogAuthStrategy, + IcebergCatalogReconciler, + RestCatalogClient, +} from '@storage/protocols/iceberg/catalog' +import { Job, Queue as PgBossQueue, SendOptions, WorkOptions } from 'pg-boss' +import { getConfig } from '../../../config' +import { BaseEvent } from '../base-event' + +const { isMultitenant, icebergCatalogUrl, icebergCatalogAuthType } = getConfig() + +type DeleteEmptyNamespacesPayload = BasePayload + +export class ReconcileIcebergCatalog extends BaseEvent { + static queueName = 'reconcile-iceberg-catalog' + + static getQueueOptions(): PgBossQueue { + return { + name: this.queueName, + policy: 'exactly_once', + } as const + } + + static getWorkerOptions(): WorkOptions { + return { + includeMetadata: true, + } + } + + static getSendOptions(): SendOptions { + return { + expireInHours: 2, + singletonKey: 'iceberg-reconcile-catalog', + singletonHours: 12, + retryLimit: 3, + retryDelay: 5, + priority: 10, + } + } + + static async handle(job: Job) { + if (!isMultitenant) { + return + } + const restCatalog = new RestCatalogClient({ + catalogUrl: icebergCatalogUrl, + auth: getCatalogAuthStrategy(icebergCatalogAuthType), + }) + + const reconciler = new IcebergCatalogReconciler(restCatalog) + await reconciler.reconcile() + } +} diff --git a/src/storage/events/index.ts b/src/storage/events/index.ts index dac72a76e..dc83153c0 100644 --- a/src/storage/events/index.ts +++ b/src/storage/events/index.ts @@ -1,16 +1,17 @@ -export * from './lifecycle/webhook' export * from './base-event' +export * from './jwks/jwks-create-signing-secret' +export * from './jwks/jwks-roll-url-signing-key' export * from './lifecycle/bucket-created' export * from './lifecycle/bucket-deleted' export * from './lifecycle/object-created' -export * from './lifecycle/object-updated' export * from './lifecycle/object-removed' +export * from './lifecycle/object-updated' +export * from './lifecycle/webhook' +export * from './migrations/reset-migrations' +export * from './migrations/run-migrations' +export * from './objects/backup-object' export * from './objects/object-admin-delete' export * from './objects/object-admin-delete-all-before' -export * from './objects/backup-object' -export * from './migrations/run-migrations' -export * from './migrations/reset-migrations' -export * from './jwks/jwks-create-signing-secret' -export * from './pgboss/upgrade-v10' export * from './pgboss/move-jobs' +export * from './pgboss/upgrade-v10' export * from './workers' diff --git a/src/storage/events/jwks/jwks-create-signing-secret.ts b/src/storage/events/jwks/jwks-create-signing-secret.ts index c430c30a4..900e3b427 100644 --- a/src/storage/events/jwks/jwks-create-signing-secret.ts +++ b/src/storage/events/jwks/jwks-create-signing-secret.ts @@ -1,8 +1,8 @@ -import { BaseEvent } from '../base-event' -import { Job, Queue, SendOptions, WorkOptions } from 'pg-boss' +import { jwksManager } from '@internal/database' import { logger, logSchema } from '@internal/monitoring' import { BasePayload } from '@internal/queue' -import { jwksManager } from '@internal/database' +import { Job, Queue, SendOptions, WorkOptions } from 'pg-boss' +import { BaseEvent } from '../base-event' interface JwksCreateSigningSecretPayload extends BasePayload { tenantId: string diff --git a/src/storage/events/jwks/jwks-roll-url-signing-key.ts b/src/storage/events/jwks/jwks-roll-url-signing-key.ts new file mode 100644 index 000000000..6bb0fb579 --- /dev/null +++ b/src/storage/events/jwks/jwks-roll-url-signing-key.ts @@ -0,0 +1,64 @@ +import { jwksManager } from '@internal/database' +import { logger, logSchema } from '@internal/monitoring' +import { BasePayload } from '@internal/queue' +import { Job, Queue, SendOptions, WorkOptions } from 'pg-boss' +import { BaseEvent } from '../base-event' + +interface JwksRollUrlSigningKeyPayload extends BasePayload { + tenantId: string +} + +export class JwksRollUrlSigningKey extends BaseEvent { + static queueName = 'tenants-jwks-roll-url-signing-key-v1' + + static getQueueOptions(): Queue { + return { + name: this.queueName, + policy: 'exactly_once', + } as const + } + + static getWorkerOptions(): WorkOptions { + return { + includeMetadata: true, + } + } + + static getSendOptions(payload: JwksRollUrlSigningKeyPayload): SendOptions { + return { + expireInHours: 2, + singletonKey: `jwks_roll_url_signing_key_${payload.tenantId}`, + retryLimit: 3, + retryDelay: 5, + priority: 10, + } + } + + static async shouldSend() { + return true + } + + static async handle(job: Job) { + const { tenantId } = job.data + + try { + const { oldKid, newKid } = await jwksManager.rollUrlSigningJwk(tenantId) + + logSchema.info( + logger, + `[Jwks] rolled url signing key for tenant ${tenantId} (old: ${oldKid}, new: ${newKid})`, + { + type: 'jwks', + project: tenantId, + } + ) + } catch (e) { + logSchema.error(logger, `[Jwks] roll url signing key failed for tenant ${tenantId}`, { + type: 'jwks', + error: e, + project: tenantId, + }) + throw e + } + } +} diff --git a/src/storage/events/lifecycle/bucket-created.ts b/src/storage/events/lifecycle/bucket-created.ts index 6495edd0d..98a510059 100644 --- a/src/storage/events/lifecycle/bucket-created.ts +++ b/src/storage/events/lifecycle/bucket-created.ts @@ -1,14 +1,16 @@ -import { BaseEvent } from '../base-event' +import { getTenantConfig, multitenantKnex } from '@internal/database' import { BasePayload } from '@internal/queue' +import { KnexShardStoreFactory, ShardCatalog, SingleShard } from '@internal/sharding' import { BucketType } from '@storage/limits' -import { Job } from 'pg-boss' import { getCatalogAuthStrategy, TenantAwareRestCatalog } from '@storage/protocols/iceberg/catalog' import { KnexMetastore } from '@storage/protocols/iceberg/knex' -import { getTenantConfig, multitenantKnex } from '@internal/database' +import { Job } from 'pg-boss' import { getConfig } from '../../../config' +import { BaseEvent } from '../base-event' interface ObjectCreatedEvent extends BasePayload { bucketId: string + bucketName: string type: BucketType } @@ -38,7 +40,12 @@ export class BucketCreatedEvent extends BaseEvent { maxCatalogsCount: features.icebergCatalog.maxCatalogs, }, restCatalogUrl: icebergCatalogUrl, - warehouse: icebergWarehouse, + sharding: isMultitenant + ? new ShardCatalog(new KnexShardStoreFactory(multitenantKnex)) + : new SingleShard({ + shardKey: icebergWarehouse, + capacity: 10000, + }), auth: catalogAuthType, metastore: new KnexMetastore(multitenantKnex, { multiTenant: true, @@ -48,6 +55,7 @@ export class BucketCreatedEvent extends BaseEvent { await restCatalog.registerCatalog({ bucketId: job.data.bucketId, + bucketName: job.data.bucketName, tenantId: job.data.tenant.ref, }) } diff --git a/src/storage/events/lifecycle/bucket-deleted.ts b/src/storage/events/lifecycle/bucket-deleted.ts index 6309df577..af3a69f4b 100644 --- a/src/storage/events/lifecycle/bucket-deleted.ts +++ b/src/storage/events/lifecycle/bucket-deleted.ts @@ -1,11 +1,12 @@ -import { BaseEvent } from '../base-event' +import { multitenantKnex } from '@internal/database' +import { ErrorCode, StorageBackendError } from '@internal/errors' import { BasePayload } from '@internal/queue' +import { DeleteIcebergResources } from '@storage/events/iceberg/delete-iceberg-resources' import { BucketType } from '@storage/limits' -import { Job } from 'pg-boss' import { KnexMetastore } from '@storage/protocols/iceberg/knex' -import { multitenantKnex } from '@internal/database' +import { Job } from 'pg-boss' import { getConfig } from '../../../config' -import { ERRORS } from '@internal/errors' +import { BaseEvent } from '../base-event' interface BucketDeletedEvent extends BasePayload { bucketId: string @@ -15,7 +16,7 @@ interface BucketDeletedEvent extends BasePayload { const { isMultitenant } = getConfig() export class BucketDeleted extends BaseEvent { - protected static queueName = 'bucket:created' + protected static queueName = 'bucket:deleted' static eventName() { return `Bucket:Deleted` @@ -26,23 +27,52 @@ export class BucketDeleted extends BaseEvent { return } - const db = isMultitenant - ? multitenantKnex - : (await this.createStorage(job.data)).db.connection.pool.acquire() + const bucketId = job.data.bucketId + + const storage = await this.createStorage(job.data) + const db = isMultitenant ? multitenantKnex : storage.db.connection.pool.acquire() const metastore = new KnexMetastore(db, { multiTenant: isMultitenant, schema: isMultitenant ? 'public' : 'storage', }) - const resources = await metastore.countResources({ - tenantId: job.data.tenant.ref, - bucketId: job.data.bucketId, - limit: 1000, - }) + await metastore.transaction(async (metastoreTx) => { + if (isMultitenant) { + try { + await metastoreTx.findCatalogById({ + id: bucketId, + tenantId: job.data.tenant.ref, + deleted: true, + }) + } catch (e) { + if (e instanceof StorageBackendError && e.code === ErrorCode.NoSuchCatalog) { + await storage.db.deleteAnalyticsBucket(bucketId) + return + } + throw e + } + } - if (resources.namespaces > 0 || resources.tables > 0) { - throw ERRORS.BucketNotEmpty(job.data.bucketId) - } + await metastoreTx.dropCatalog({ + bucketId, + tenantId: job.data.tenant.ref, + soft: true, + }) + + await DeleteIcebergResources.send( + { + tenant: job.data.tenant, + catalogId: job.data.bucketId, + }, + { + tnx: isMultitenant ? metastoreTx.getTnx() : undefined, + } + ) + + if (isMultitenant) { + await storage.db.deleteAnalyticsBucket(bucketId, { soft: true }) + } + }) } } diff --git a/src/storage/events/lifecycle/object-created.ts b/src/storage/events/lifecycle/object-created.ts index be8660036..aa1cc1477 100644 --- a/src/storage/events/lifecycle/object-created.ts +++ b/src/storage/events/lifecycle/object-created.ts @@ -1,6 +1,6 @@ import { BasePayload } from '@internal/queue' -import { BaseEvent } from '../base-event' import { ObjectMetadata } from '../../backend' +import { BaseEvent } from '../base-event' import { ObjectRemovedEvent } from './object-removed' interface ObjectCreatedEvent extends BasePayload { diff --git a/src/storage/events/lifecycle/object-removed.ts b/src/storage/events/lifecycle/object-removed.ts index 982b35083..7e7b3c2e9 100644 --- a/src/storage/events/lifecycle/object-removed.ts +++ b/src/storage/events/lifecycle/object-removed.ts @@ -1,6 +1,6 @@ import { BasePayload } from '@internal/queue' -import { BaseEvent } from '../base-event' import { ObjectMetadata } from '@storage/backend' +import { BaseEvent } from '../base-event' export interface ObjectRemovedEvent extends BasePayload { name: string diff --git a/src/storage/events/lifecycle/object-updated.ts b/src/storage/events/lifecycle/object-updated.ts index 33bf25735..a94f0b672 100644 --- a/src/storage/events/lifecycle/object-updated.ts +++ b/src/storage/events/lifecycle/object-updated.ts @@ -1,6 +1,6 @@ import { BasePayload } from '@internal/queue' -import { BaseEvent } from '../base-event' import { ObjectMetadata } from '../../backend' +import { BaseEvent } from '../base-event' interface ObjectUpdatedMetadataEvent extends BasePayload { name: string diff --git a/src/storage/events/lifecycle/webhook.ts b/src/storage/events/lifecycle/webhook.ts index 1300424de..782e50453 100644 --- a/src/storage/events/lifecycle/webhook.ts +++ b/src/storage/events/lifecycle/webhook.ts @@ -1,11 +1,10 @@ -import { BaseEvent } from '../base-event' -import { Job, SendOptions, WorkOptions } from 'pg-boss' -import { HttpsAgent } from 'agentkeepalive' -import HttpAgent from 'agentkeepalive' +import { getTenantConfig } from '@internal/database' +import { logger, logSchema } from '@internal/monitoring' +import HttpAgent, { HttpsAgent } from 'agentkeepalive' import axios from 'axios' +import { Job, SendOptions, WorkOptions } from 'pg-boss' import { getConfig } from '../../../config' -import { logger, logSchema } from '@internal/monitoring' -import { getTenantConfig } from '@internal/database' +import { BaseEvent } from '../base-event' const { isMultitenant, diff --git a/src/storage/events/migrations/reset-migrations.ts b/src/storage/events/migrations/reset-migrations.ts index 0c735ab01..c54eda611 100644 --- a/src/storage/events/migrations/reset-migrations.ts +++ b/src/storage/events/migrations/reset-migrations.ts @@ -1,10 +1,10 @@ -import { BaseEvent } from '../base-event' import { getTenantConfig } from '@internal/database' -import { JobWithMetadata, Queue, SendOptions, WorkOptions } from 'pg-boss' -import { BasePayload } from '@internal/queue' import { DBMigration, resetMigration } from '@internal/database/migrations' -import { RunMigrationsOnTenants } from './run-migrations' import { logger, logSchema } from '@internal/monitoring' +import { BasePayload } from '@internal/queue' +import { JobWithMetadata, Queue, SendOptions, WorkOptions } from 'pg-boss' +import { BaseEvent } from '../base-event' +import { RunMigrationsOnTenants } from './run-migrations' interface ResetMigrationsPayload extends BasePayload { tenantId: string @@ -48,7 +48,7 @@ export class ResetMigrationsOnTenant extends BaseEvent { }) const reset = await resetMigration({ - tenantId: tenantId, + tenantId, markCompletedTillMigration: job.data.markCompletedTillMigration, untilMigration: job.data.untilMigration, databaseUrl: tenant.databaseUrl, @@ -56,7 +56,7 @@ export class ResetMigrationsOnTenant extends BaseEvent { if (reset) { await RunMigrationsOnTenants.send({ - tenantId: tenantId, + tenantId, tenant: { ref: tenantId, }, diff --git a/src/storage/events/migrations/run-migrations.test.ts b/src/storage/events/migrations/run-migrations.test.ts new file mode 100644 index 000000000..2aa668030 --- /dev/null +++ b/src/storage/events/migrations/run-migrations.test.ts @@ -0,0 +1,179 @@ +import { vi } from 'vitest' + +const { + mockGetTenantConfig, + mockDeleteTenantConfig, + mockAreMigrationsUpToDate, + mockRunMigrationsOnTenant, + mockUpdateTenantMigrationsState, + mockDeleteIfActiveExists, + mockInfo, + mockError, +} = vi.hoisted(() => ({ + mockGetTenantConfig: vi.fn(), + mockDeleteTenantConfig: vi.fn(), + mockAreMigrationsUpToDate: vi.fn(), + mockRunMigrationsOnTenant: vi.fn(), + mockUpdateTenantMigrationsState: vi.fn(), + mockDeleteIfActiveExists: vi.fn(), + mockInfo: vi.fn(), + mockError: vi.fn(), +})) + +vi.mock('@internal/database', () => ({ + deleteTenantConfig: mockDeleteTenantConfig, + getTenantConfig: mockGetTenantConfig, + TenantMigrationStatus: { + COMPLETED: 'COMPLETED', + FAILED: 'FAILED', + FAILED_STALE: 'FAILED_STALE', + }, +})) + +vi.mock('@internal/database/migrations', () => ({ + areMigrationsUpToDate: mockAreMigrationsUpToDate, + runMigrationsOnTenant: mockRunMigrationsOnTenant, + updateTenantMigrationsState: mockUpdateTenantMigrationsState, +})) + +vi.mock('../base-event', () => ({ + BaseEvent: class { + static deleteIfActiveExists = mockDeleteIfActiveExists + + static getQueueName(this: { queueName: string }) { + return this.queueName + } + }, +})) + +vi.mock('@internal/monitoring', () => ({ + logger: {}, + logSchema: { + info: mockInfo, + error: mockError, + warning: vi.fn(), + }, +})) + +import { TenantMigrationStatus } from '@internal/database' +import { ERRORS } from '@internal/errors' +import { RunMigrationsOnTenants } from './run-migrations' + +function makeJob(overrides?: Partial>) { + return { + id: 'job-1', + name: RunMigrationsOnTenants.getQueueName(), + retryCount: 0, + retryLimit: 3, + singletonKey: 'migrations_tenant-a', + data: { + tenantId: 'tenant-a', + upToMigration: 'storage-schema', + tenant: { + ref: 'tenant-a', + host: '', + }, + }, + ...overrides, + } +} + +describe('RunMigrationsOnTenants.handle', () => { + beforeEach(() => { + vi.clearAllMocks() + + mockGetTenantConfig.mockResolvedValue({ + databaseUrl: 'postgres://tenant-db', + }) + mockAreMigrationsUpToDate.mockResolvedValue(false) + mockRunMigrationsOnTenant.mockResolvedValue(undefined) + mockUpdateTenantMigrationsState.mockResolvedValue(undefined) + mockDeleteIfActiveExists.mockResolvedValue(undefined) + }) + + it('runs migrations and marks the tenant completed on success', async () => { + await expect(RunMigrationsOnTenants.handle(makeJob() as never)).resolves.toBeUndefined() + + expect(mockDeleteTenantConfig).toHaveBeenCalledWith('tenant-a') + expect(mockDeleteTenantConfig.mock.invocationCallOrder[0]).toBeLessThan( + mockGetTenantConfig.mock.invocationCallOrder[0] + ) + expect(mockRunMigrationsOnTenant).toHaveBeenCalledWith({ + databaseUrl: 'postgres://tenant-db', + tenantId: 'tenant-a', + waitForLock: false, + upToMigration: 'storage-schema', + }) + expect(mockUpdateTenantMigrationsState).toHaveBeenCalledWith('tenant-a', { + migration: 'storage-schema', + state: TenantMigrationStatus.COMPLETED, + }) + expect(mockDeleteIfActiveExists).not.toHaveBeenCalled() + }) + + it('short-circuits when migrations are already up to date', async () => { + mockAreMigrationsUpToDate.mockResolvedValue(true) + + await expect(RunMigrationsOnTenants.handle(makeJob() as never)).resolves.toBeUndefined() + + expect(mockRunMigrationsOnTenant).not.toHaveBeenCalled() + expect(mockUpdateTenantMigrationsState).not.toHaveBeenCalled() + expect(mockDeleteIfActiveExists).not.toHaveBeenCalled() + }) + + it('returns without marking the tenant failed on lock timeout', async () => { + mockRunMigrationsOnTenant.mockRejectedValue(ERRORS.LockTimeout()) + + await expect(RunMigrationsOnTenants.handle(makeJob() as never)).resolves.toBeUndefined() + + expect(mockUpdateTenantMigrationsState).not.toHaveBeenCalled() + expect(mockDeleteIfActiveExists).not.toHaveBeenCalled() + expect(mockInfo).toHaveBeenCalledWith( + expect.anything(), + '[Migrations] lock timeout for tenant tenant-a', + expect.objectContaining({ + type: 'migrations', + project: 'tenant-a', + }) + ) + }) + + it('marks the tenant FAILED and rethrows when a retryable failure happens', async () => { + mockRunMigrationsOnTenant.mockRejectedValue(new Error('migration failed')) + + await expect(RunMigrationsOnTenants.handle(makeJob() as never)).rejects.toThrow( + 'migration failed' + ) + + expect(mockUpdateTenantMigrationsState).toHaveBeenCalledWith('tenant-a', { + state: TenantMigrationStatus.FAILED, + }) + expect(mockDeleteIfActiveExists).toHaveBeenCalledWith( + RunMigrationsOnTenants.getQueueName(), + 'migrations_tenant-a', + 'job-1' + ) + }) + + it('marks the tenant FAILED_STALE on the final retry before rethrowing', async () => { + mockRunMigrationsOnTenant.mockRejectedValue(new Error('migration failed')) + + await expect( + RunMigrationsOnTenants.handle( + makeJob({ + retryCount: 3, + retryLimit: 3, + }) as never + ) + ).rejects.toThrow('migration failed') + + expect(mockUpdateTenantMigrationsState).toHaveBeenCalledWith('tenant-a', { + state: TenantMigrationStatus.FAILED_STALE, + }) + expect(mockDeleteIfActiveExists).toHaveBeenCalledWith( + RunMigrationsOnTenants.getQueueName(), + 'migrations_tenant-a', + 'job-1' + ) + }) +}) diff --git a/src/storage/events/migrations/run-migrations.ts b/src/storage/events/migrations/run-migrations.ts index 6e663bd85..0aeca7445 100644 --- a/src/storage/events/migrations/run-migrations.ts +++ b/src/storage/events/migrations/run-migrations.ts @@ -1,8 +1,4 @@ -import { BaseEvent } from '../base-event' -import { getTenantConfig, TenantMigrationStatus } from '@internal/database' -import { JobWithMetadata, Queue, SendOptions, WorkOptions } from 'pg-boss' -import { logger, logSchema } from '@internal/monitoring' -import { BasePayload } from '@internal/queue' +import { deleteTenantConfig, getTenantConfig, TenantMigrationStatus } from '@internal/database' import { areMigrationsUpToDate, DBMigration, @@ -10,6 +6,10 @@ import { updateTenantMigrationsState, } from '@internal/database/migrations' import { ErrorCode, StorageBackendError } from '@internal/errors' +import { logger, logSchema } from '@internal/monitoring' +import { BasePayload } from '@internal/queue' +import { JobWithMetadata, Queue, SendOptions, WorkOptions } from 'pg-boss' +import { BaseEvent } from '../base-event' interface RunMigrationsPayload extends BasePayload { tenantId: string @@ -37,6 +37,7 @@ export class RunMigrationsOnTenants extends BaseEvent { return { singletonKey: `migrations_${payload.tenantId}`, singletonHours: 1, + expireInMinutes: 10, retryLimit: 3, retryDelay: 5, priority: 10, @@ -45,6 +46,7 @@ export class RunMigrationsOnTenants extends BaseEvent { static async handle(job: JobWithMetadata) { const tenantId = job.data.tenant.ref + deleteTenantConfig(tenantId) const tenant = await getTenantConfig(tenantId) const migrationsUpToDate = await areMigrationsUpToDate(tenantId) diff --git a/src/storage/events/objects/backup-object.ts b/src/storage/events/objects/backup-object.ts index e14f07f4b..a63f00bf8 100644 --- a/src/storage/events/objects/backup-object.ts +++ b/src/storage/events/objects/backup-object.ts @@ -1,9 +1,9 @@ -import { BaseEvent } from '../base-event' -import { JobWithMetadata, Queue, SendOptions, WorkOptions } from 'pg-boss' +import { logger, logSchema } from '@internal/monitoring' import { BasePayload } from '@internal/queue' import { S3Backend } from '@storage/backend' +import { JobWithMetadata, Queue, SendOptions, WorkOptions } from 'pg-boss' import { getConfig } from '../../../config' -import { logger, logSchema } from '@internal/monitoring' +import { BaseEvent } from '../base-event' const { storageS3Bucket } = getConfig() diff --git a/src/storage/events/objects/object-admin-delete-all-before.ts b/src/storage/events/objects/object-admin-delete-all-before.ts index ef70e8edd..fd205c6b3 100644 --- a/src/storage/events/objects/object-admin-delete-all-before.ts +++ b/src/storage/events/objects/object-admin-delete-all-before.ts @@ -1,10 +1,11 @@ -import { BaseEvent } from '../base-event' -import { getConfig } from '../../../config' -import { Job, SendOptions, WorkOptions } from 'pg-boss' import { logger, logSchema } from '@internal/monitoring' -import { Storage } from '../../index' import { BasePayload } from '@internal/queue' import { withOptionalVersion } from '@storage/backend' +import { Job, SendOptions, WorkOptions } from 'pg-boss' +import { getConfig } from '../../../config' +import { Storage } from '../../index' +import { BaseEvent } from '../base-event' +import { ObjectRemoved } from '../lifecycle/object-removed' const DELETE_JOB_TIME_LIMIT_MS = 10_000 @@ -87,6 +88,19 @@ export class ObjectAdminDeleteAllBefore extends BaseEvent + ObjectRemoved.sendWebhook({ + tenant: job.data.tenant, + name: object.name, + bucketId, + reqId: job.data.reqId, + version: object.version, + metadata: object.metadata, + }) + ) + ) } }) } diff --git a/src/storage/events/objects/object-admin-delete.ts b/src/storage/events/objects/object-admin-delete.ts index 02b8142b4..f6de19fbe 100644 --- a/src/storage/events/objects/object-admin-delete.ts +++ b/src/storage/events/objects/object-admin-delete.ts @@ -1,10 +1,10 @@ -import { BaseEvent } from '../base-event' -import { getConfig } from '../../../config' +import { logger, logSchema } from '@internal/monitoring' +import { BasePayload } from '@internal/queue' import { Job, SendOptions, WorkOptions } from 'pg-boss' +import { getConfig } from '../../../config' import { withOptionalVersion } from '../../backend' -import { logger, logSchema } from '@internal/monitoring' import { Storage } from '../../index' -import { BasePayload } from '@internal/queue' +import { BaseEvent } from '../base-event' export interface ObjectDeleteEvent extends BasePayload { name: string diff --git a/src/storage/events/pgboss/move-jobs.ts b/src/storage/events/pgboss/move-jobs.ts index 02c081b30..49369d3c2 100644 --- a/src/storage/events/pgboss/move-jobs.ts +++ b/src/storage/events/pgboss/move-jobs.ts @@ -1,8 +1,8 @@ -import { BaseEvent } from '../base-event' -import { Job, Queue as PgBossQueue, SendOptions, WorkOptions } from 'pg-boss' -import { BasePayload, Queue } from '@internal/queue' import { multitenantKnex } from '@internal/database' import { logger, logSchema } from '@internal/monitoring' +import { BasePayload, PG_BOSS_SCHEMA, Queue } from '@internal/queue' +import { Job, Queue as PgBossQueue, SendOptions, WorkOptions } from 'pg-boss' +import { BaseEvent } from '../base-event' interface MoveJobsPayload extends BasePayload { fromQueue: string @@ -46,7 +46,7 @@ export class MoveJobs extends BaseEvent { return } - const schema = 'pgboss_v10' + const schema = PG_BOSS_SCHEMA const fromQueueName = job.data.fromQueue const toQueue = await Queue.getInstance().getQueue(job.data.toQueue) @@ -78,9 +78,9 @@ export class MoveJobs extends BaseEvent { policy, state ) - SELECT + SELECT id, - '${toQueue.name}' as name, + ? as name, priority, data, retry_limit, @@ -94,23 +94,23 @@ export class MoveJobs extends BaseEvent { created_on, keep_until, output, - '${toQueue.policy}' as policy, + ? as policy, 'created' as state FROM ${schema}.job - WHERE name = '${fromQueueName}' + WHERE name = ? AND state IN ('created', 'active', 'retry') ON CONFLICT DO NOTHING ` - await tnx.raw(sql) + await tnx.raw(sql, [toQueue.name, toQueue.policy, fromQueueName]) if (job.data.deleteJobsFromOriginalQueue) { const deleteSql = ` DELETE FROM ${schema}.job - WHERE name = '${fromQueueName}' + WHERE name = ? AND state IN ('created', 'active', 'retry') ` - await tnx.raw(deleteSql) + await tnx.raw(deleteSql, [fromQueueName]) } } catch (error) { logSchema.error(logger, '[PgBoss] Error while copying jobs', { diff --git a/src/storage/events/pgboss/upgrade-v10.ts b/src/storage/events/pgboss/upgrade-v10.ts index 827a72b38..4231679e3 100644 --- a/src/storage/events/pgboss/upgrade-v10.ts +++ b/src/storage/events/pgboss/upgrade-v10.ts @@ -1,8 +1,8 @@ -import { BaseEvent } from '../base-event' -import { Job, Queue as PgBossQueue, SendOptions, WorkOptions } from 'pg-boss' -import { BasePayload, Queue } from '@internal/queue' import { multitenantKnex } from '@internal/database' import { logger, logSchema } from '@internal/monitoring' +import { BasePayload, PG_BOSS_SCHEMA, Queue } from '@internal/queue' +import { Job, Queue as PgBossQueue, SendOptions, WorkOptions } from 'pg-boss' +import { BaseEvent } from '../base-event' type UpgradePgBossV10Payload = BasePayload @@ -42,7 +42,7 @@ export class UpgradePgBossV10 extends BaseEvent { return } - const targetSchema = 'pgboss_v10' + const targetSchema = PG_BOSS_SCHEMA const sourceSchema = 'pgboss' const queues = await Queue.getInstance().getQueues() @@ -84,14 +84,14 @@ export class UpgradePgBossV10 extends BaseEvent { createdOn, keepUntil, output jsonb, - '${queue.policy}' as policy + ? as policy FROM ${sourceSchema}.job - WHERE name = '${queue.name}' + WHERE name = ? AND state = 'created' ON CONFLICT DO NOTHING ` - await multitenantKnex.raw(sql) + await tnx.raw(sql, [queue.policy, queue.name]) } catch (error) { logSchema.error(logger, '[PgBoss] Error while copying jobs', { type: 'pgboss', diff --git a/src/storage/events/upgrades/base-event.ts b/src/storage/events/upgrades/base-event.ts new file mode 100644 index 000000000..ff6b11982 --- /dev/null +++ b/src/storage/events/upgrades/base-event.ts @@ -0,0 +1,93 @@ +import { multitenantKnex } from '@internal/database' +import { hashStringToInt } from '@internal/hashing' +import { logger, logSchema } from '@internal/monitoring' +import { BasePayload } from '@internal/queue' +import { BaseEvent } from '@storage/events' +import { Knex } from 'knex' +import { Job, Queue as PgBossQueue, SendOptions } from 'pg-boss' +import { getConfig } from '../../../config' + +const { isMultitenant } = getConfig() + +export type UpgradeBaseEventPayload = BasePayload + +export abstract class UpgradeBaseEvent extends BaseEvent { + static getQueueOptions(): PgBossQueue { + return { + name: this.queueName, + policy: 'exactly_once', + } as const + } + + static getSendOptions(): SendOptions { + return { + expireInHours: 2, + singletonKey: this.getQueueName(), + singletonHours: 12, + retryBackoff: false, + retryLimit: 3, + retryDelay: 5, + priority: 10, + } + } + + static async handleUpgrade( + tnx: Knex.Transaction, + job: Job + ): Promise {} + + static async handle(job: Job) { + if (!isMultitenant) { + return + } + + await this.runOnce((t: Knex.Transaction) => { + return this.handleUpgrade(t, job) + }) + } + + protected static async runOnce(fn: (t: Knex.Transaction) => Promise | void) { + logSchema.info(logger, `[Upgrade] Starting upgrade event: ${this.getQueueName()}`, { + type: 'upgradeEvent', + }) + await multitenantKnex.transaction(async (t) => { + const hash = hashStringToInt('event:upgrade-lock') + const result = await t.raw<{ rows: { pg_try_advisory_xact_lock: boolean }[] }>( + `SELECT pg_try_advisory_xact_lock(?);`, + [hash] + ) + const lockAcquired = result.rows.shift()?.pg_try_advisory_xact_lock || false + + if (!lockAcquired) { + logSchema.info(logger, `[Upgrade] Lock already acquired for: ${this.getQueueName()}`, { + type: 'upgradeEvent', + }) + return + } + + const id = await t + .table('event_upgrades') + .select('event_id') + .where('event_id', this.getQueueName()) + .first() + + if (id) { + return + } + + await fn(t) + + await t + .table('event_upgrades') + .insert({ + event_id: this.getQueueName(), + }) + .onConflict('event_id') + .ignore() + + logSchema.info(logger, `[Upgrade] Completed upgrade: ${this.getQueueName()}`, { + type: 'upgradeEvent', + }) + }) + } +} diff --git a/src/storage/events/upgrades/sync-catalog-ids.ts b/src/storage/events/upgrades/sync-catalog-ids.ts new file mode 100644 index 000000000..5dba1d452 --- /dev/null +++ b/src/storage/events/upgrades/sync-catalog-ids.ts @@ -0,0 +1,178 @@ +import { getTenantConfig } from '@internal/database' +import { runMigrationsOnTenant } from '@internal/database/migrations' +import { logger, logSchema } from '@internal/monitoring' +import { Knex } from 'knex' +import { getConfig } from '../../../config' +import { UpgradeBaseEvent, UpgradeBaseEventPayload } from './base-event' + +type SyncCatalogIdsPayload = UpgradeBaseEventPayload + +const { icebergShards } = getConfig() + +export class SyncCatalogIds extends UpgradeBaseEvent { + static queueName = 'sync-iceberg-catalog-ids' + + static async handleUpgrade(tnx: Knex.Transaction) { + const tenantCatalogs = await tnx + .table('iceberg_catalogs') + .select<{ tenant_id: string; catalog_names: string[] }[]>( + 'tenant_id', + tnx.raw('ARRAY_AGG(name) AS catalog_names') + ) + .groupBy('tenant_id') + + let updatedCount = 0 + let hardFail: unknown | undefined = undefined + + logSchema.info( + logger, + `[Upgrade][SyncCatalogIds] Found ${tenantCatalogs.length} catalogs to sync IDs for`, + { + type: 'upgradeEvent', + } + ) + + await Promise.all( + tenantCatalogs.map(async (catalog) => { + try { + const config = await getTenantConfig(catalog.tenant_id) + + await runMigrationsOnTenant({ + tenantId: catalog.tenant_id, + databaseUrl: config.databaseUrl, + waitForLock: true, + }) + + const storage = await this.createStorage({ + tenant: { + ref: catalog.tenant_id, + host: '', + }, + }) + + const tenantBuckets = await storage.listAnalyticsBuckets('id,name', { + limit: 1000, + }) + + logSchema.info( + logger, + `[Upgrade][SyncCatalogIds] Found ${tenantBuckets.length} buckets to sync for tenant ${catalog.tenant_id}`, + { + type: 'upgradeEvent', + } + ) + + if (hardFail) { + throw hardFail + } + + try { + for (const bucket of tenantBuckets) { + const updated = await tnx + .table('iceberg_catalogs') + .where('tenant_id', catalog.tenant_id) + .where('name', bucket.name) + .whereNull('deleted_at') + .update({ + id: bucket.id, + updated_at: new Date(), + }) + .returning('id') + + logSchema.info( + logger, + `[Upgrade][SyncCatalogIds] Updated ${updated.length} records for bucket ${bucket.name} for tenant_id ${catalog.tenant_id} using catalog-id ${bucket.id}`, + { + type: 'upgradeEvent', + } + ) + + if (updated.length === 0) { + // insert catalog if it does not exist + await tnx.table('iceberg_catalogs').insert({ + id: bucket.id, + name: bucket.name, + tenant_id: catalog.tenant_id, + created_at: new Date(), + updated_at: new Date(), + }) + } + + updatedCount += updated.length + } + } catch (e) { + logSchema.error( + logger, + `[Upgrade][SyncCatalogIds] Failed to update bucket for ${catalog.tenant_id}`, + { + type: 'upgradeEvent', + error: e, + } + ) + + if (hardFail) { + hardFail = e + } + throw e + } + } catch (e) { + // no-op + logSchema.error( + logger, + `[Upgrade][SyncCatalogIds] Error interacting with tenant ${catalog.tenant_id}, skipping...`, + { + error: e, + type: 'upgradeEvent', + } + ) + } + }) + ) + + if (hardFail) { + throw hardFail + } + + if (updatedCount > 0) { + await this.refillShards(tnx) + } + + logSchema.info(logger, `[Upgrade][SyncCatalogIds] Completed updated ${updatedCount} catalogs`, { + type: 'upgradeEvent', + }) + } + + protected static async refillShards(tnx: Knex.Transaction) { + if (icebergShards.length === 0) { + return + } + + await tnx.raw(`DELETE FROM shard_reservation where resource_id LIKE 'iceberg-table::%'`) + await tnx.raw(`DELETE FROM shard_slots where resource_id LIKE 'iceberg-table::%'`) + + const query = ` + WITH all_iceberg_tables as ( + SELECT t.id, t.tenant_id, t.name, t.shard_key, t.shard_id, t.namespace_id, t.catalog_id, row_number() OVER () as seq_num + FROM iceberg_tables t + ), + set_shard_reservation AS ( + INSERT INTO shard_reservation (tenant_id, shard_id, kind, resource_id, lease_expires_at, slot_no, status) + SELECT it.tenant_id, s.id, 'iceberg-table', ('iceberg-table::' || it.catalog_id || '::' || it.namespace_id || '/' || it.name), now() + interval '5 minutes', it.seq_num - 1, 'confirmed' + FROM all_iceberg_tables it + JOIN shard s ON s.kind = 'iceberg-table' AND s.shard_key = it.shard_key + ), + shard_slot AS ( + INSERT INTO shard_slots (tenant_id, shard_id, resource_id, slot_no) + SELECT it.tenant_id, it.shard_id, ('iceberg-table::' || it.catalog_id || '::' || it.namespace_id || '/' || it.name), it.seq_num - 1 + FROM all_iceberg_tables it + RETURNING slot_no + ) + UPDATE shard + SET next_slot = (SELECT COALESCE((SELECT MAX(slot_no) + 1 FROM shard_slot), next_slot)) + WHERE shard.kind = 'iceberg-table' + AND shard.shard_key = ?; + ` + + await tnx.raw(query, icebergShards[0]) + } +} diff --git a/src/storage/events/workers.ts b/src/storage/events/workers.ts index 99d4929e2..74b6bb9f6 100644 --- a/src/storage/events/workers.ts +++ b/src/storage/events/workers.ts @@ -1,13 +1,17 @@ import { Queue } from '@internal/queue' +import { DeleteIcebergResources } from './iceberg/delete-iceberg-resources' +import { ReconcileIcebergCatalog } from './iceberg/reconcile-catalog' +import { JwksCreateSigningSecret } from './jwks/jwks-create-signing-secret' +import { JwksRollUrlSigningKey } from './jwks/jwks-roll-url-signing-key' import { Webhook } from './lifecycle/webhook' -import { ObjectAdminDelete } from './objects/object-admin-delete' +import { ResetMigrationsOnTenant } from './migrations/reset-migrations' import { RunMigrationsOnTenants } from './migrations/run-migrations' import { BackupObjectEvent } from './objects/backup-object' -import { ResetMigrationsOnTenant } from './migrations/reset-migrations' -import { JwksCreateSigningSecret } from './jwks/jwks-create-signing-secret' -import { UpgradePgBossV10 } from './pgboss/upgrade-v10' -import { MoveJobs } from './pgboss/move-jobs' +import { ObjectAdminDelete } from './objects/object-admin-delete' import { ObjectAdminDeleteAllBefore } from './objects/object-admin-delete-all-before' +import { MoveJobs } from './pgboss/move-jobs' +import { UpgradePgBossV10 } from './pgboss/upgrade-v10' +import { SyncCatalogIds } from './upgrades/sync-catalog-ids' export function registerWorkers() { Queue.register(Webhook) @@ -17,6 +21,10 @@ export function registerWorkers() { Queue.register(BackupObjectEvent) Queue.register(ResetMigrationsOnTenant) Queue.register(JwksCreateSigningSecret) + Queue.register(JwksRollUrlSigningKey) Queue.register(UpgradePgBossV10) Queue.register(MoveJobs) + Queue.register(ReconcileIcebergCatalog) + Queue.register(DeleteIcebergResources) + Queue.register(SyncCatalogIds) } diff --git a/src/storage/index.ts b/src/storage/index.ts index 47e06ce54..c3ff68510 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -1,5 +1,5 @@ -export * from './storage' export * as backends from './backend' export * from './database' -export * from './schemas' export * from './scanner/scanner' +export * from './schemas' +export * from './storage' diff --git a/src/storage/limits.ts b/src/storage/limits.ts index 562f8e816..35ca85732 100644 --- a/src/storage/limits.ts +++ b/src/storage/limits.ts @@ -1,9 +1,9 @@ +import { ERRORS } from '@internal/errors' import { getConfig } from '../config' import { - getFileSizeLimit as getFileSizeLimitForTenant, getFeatures, + getFileSizeLimit as getFileSizeLimitForTenant, } from '../internal/database/tenant' -import { ERRORS } from '../internal/errors' const { isMultitenant, imageTransformationEnabled, icebergBucketDetectionSuffix } = getConfig() @@ -113,7 +113,6 @@ export function parseFileSizeToBytes(valueWithUnit: string) { throw ERRORS.InvalidFileSizeLimit() } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const [, valueS, unit] = valueWithUnit.match(valuesRegex)! const value = +parseFloat(valueS).toPrecision(3) diff --git a/src/storage/object.ts b/src/storage/object.ts index 6922ecf47..1529919e5 100644 --- a/src/storage/object.ts +++ b/src/storage/object.ts @@ -1,13 +1,13 @@ import { randomUUID } from 'node:crypto' import { SignedUploadToken, signJWT, verifyJWT } from '@internal/auth' -import { ERRORS } from '@internal/errors' import { getJwtSecret } from '@internal/database' - +import { ERRORS } from '@internal/errors' +import { StorageObjectLocator } from '@storage/locator' +import { Obj } from '@storage/schemas' +import { FastifyRequest } from 'fastify/types/request' +import { getConfig } from '../config' import { ObjectMetadata, StorageBackendAdapter } from './backend' import { Database, FindObjectFilters, SearchObjectOption } from './database' -import { mustBeValidKey } from './limits' -import { fileUploadFromRequest, Uploader, UploadRequest } from './uploader' -import { getConfig } from '../config' import { ObjectAdminDelete, ObjectCreatedCopyEvent, @@ -16,9 +16,8 @@ import { ObjectRemovedMove, ObjectUpdatedMetadata, } from './events' -import { FastifyRequest } from 'fastify/types/request' -import { Obj } from '@storage/schemas' -import { StorageObjectLocator } from '@storage/locator' +import { mustBeValidKey } from './limits' +import { CanUploadMetadata, fileUploadFromRequest, Uploader, UploadRequest } from './uploader' const { requestUrlLengthLimit } = getConfig() @@ -41,6 +40,13 @@ interface CopyObjectParams { ifUnmodifiedSince?: Date } } +export interface ListObjectsV2Result { + folders: Obj[] + objects: Obj[] + hasNext: boolean + nextCursor?: string + nextCursorKey?: string +} /** * ObjectStorage @@ -91,6 +97,7 @@ export class ObjectStorage { owner: file.owner, isUpsert: Boolean(file.isUpsert), signal: file.signal, + userMetadata: uploadRequest.userMetadata, }) } @@ -318,7 +325,6 @@ export class ObjectStorage { 'bucket_id,metadata,user_metadata,version' ) - // eslint-disable-next-line @typescript-eslint/no-unused-vars const baseMetadata = originObject.metadata || {} const destinationMetadata = copyMetadata ? baseMetadata @@ -327,11 +333,15 @@ export class ObjectStorage { ...(fileMetadata || {}), } + const destinationUserMetadata = copyMetadata ? originObject.user_metadata : userMetadata + await this.uploader.canUpload({ bucketId: destinationBucket, objectName: destinationKey, owner, isUpsert: upsert, + userMetadata: destinationUserMetadata || undefined, + metadata: destinationMetadata, }) try { @@ -376,7 +386,7 @@ export class ObjectStorage { lastModified: copyResult.lastModified, eTag: copyResult.eTag, }, - user_metadata: copyMetadata ? originObject.user_metadata : userMetadata, + user_metadata: destinationUserMetadata, version: newVersion, }) @@ -397,7 +407,7 @@ export class ObjectStorage { tenant: this.db.tenant(), name: destinationKey, version: newVersion, - bucketId: this.bucketId, + bucketId: destinationBucket, metadata, reqId: this.db.reqId, }) @@ -504,7 +514,7 @@ export class ObjectStorage { name: destinationObjectName, bucket_id: destinationBucket, version: newVersion, - owner: owner, + owner, metadata, user_metadata: sourceObj.user_metadata, }) @@ -530,8 +540,8 @@ export class ObjectStorage { tenant: this.db.tenant(), name: destinationObjectName, version: newVersion, - bucketId: this.bucketId, - metadata: metadata, + bucketId: destinationBucket, + metadata, oldObject: { name: sourceObjectName, bucketId: this.bucketId, @@ -548,7 +558,7 @@ export class ObjectStorage { name: destinationObjectName, bucket_id: destinationBucket, version: newVersion, - owner: owner, + owner, metadata, }, } @@ -556,7 +566,7 @@ export class ObjectStorage { } catch (e) { await ObjectAdminDelete.send({ name: destinationObjectName, - bucketId: this.bucketId, + bucketId: destinationBucket, tenant: this.db.tenant(), version: newVersion, reqId: this.db.reqId, @@ -586,18 +596,27 @@ export class ObjectStorage { startAfter?: string maxKeys?: number encodingType?: 'url' - }) { + sortBy?: { + column: 'name' | 'created_at' | 'updated_at' + order?: string + } + }): Promise { const limit = Math.min(options?.maxKeys || 1000, 1000) const prefix = options?.prefix || '' const delimiter = options?.delimiter - const cursor = options?.cursor ? decodeContinuationToken(options?.cursor) : undefined + const cursor = options?.cursor ? decodeContinuationToken(options.cursor) : undefined let searchResult = await this.db.listObjectsV2(this.bucketId, { prefix: options?.prefix, delimiter: options?.delimiter, maxKeys: limit + 1, - nextToken: cursor, - startAfter: cursor || options?.startAfter, + nextToken: cursor?.startAfter, + startAfter: cursor?.startAfter || options?.startAfter, + sortBy: { + order: cursor?.sortOrder || options?.sortBy?.order, + column: cursor?.sortColumn || options?.sortBy?.column, + after: cursor?.sortColumnAfter, + }, }) let prevPrefix = '' @@ -638,21 +657,41 @@ export class ObjectStorage { const objects: Obj[] = [] searchResult.forEach((obj) => { const target = obj.id === null ? folders : objects + const name = obj.id === null && !obj.name.endsWith('/') ? obj.name + '/' : obj.name target.push({ ...obj, - name: options?.encodingType === 'url' ? encodeURIComponent(obj.name) : obj.name, + name: options?.encodingType === 'url' ? encodeURIComponent(name) : name, }) }) - const nextContinuationToken = isTruncated - ? encodeContinuationToken(searchResult[searchResult.length - 1].name) - : undefined + let nextContinuationToken: string | undefined + let nextCursorKey: string | undefined + + if (isTruncated) { + const sortColumn = (cursor?.sortColumn || options?.sortBy?.column) as + | 'name' + | 'created_at' + | 'updated_at' + | undefined + + nextContinuationToken = encodeContinuationToken({ + startAfter: searchResult[searchResult.length - 1].name, + sortOrder: cursor?.sortOrder || options?.sortBy?.order, + sortColumn, + sortColumnAfter: + sortColumn && sortColumn !== 'name' && searchResult[searchResult.length - 1][sortColumn] + ? new Date(searchResult[searchResult.length - 1][sortColumn] || '').toISOString() + : undefined, + }) + nextCursorKey = searchResult[searchResult.length - 1].name + } return { hasNext: isTruncated, nextCursor: nextContinuationToken, - folders: folders, - objects: objects, + nextCursorKey, + folders, + objects, } } @@ -756,7 +795,11 @@ export class ObjectStorage { url: string, expiresIn: number, owner?: string, - options?: { upsert?: boolean } + options?: { + upsert?: boolean + userMetadata?: Record + metadata?: CanUploadMetadata + } ) { // check if user has INSERT permissions await this.uploader.canUpload({ @@ -764,6 +807,8 @@ export class ObjectStorage { objectName, owner, isUpsert: options?.upsert ?? false, + userMetadata: options?.userMetadata, + metadata: options?.metadata, }) const { urlSigningKey } = await getJwtSecret(this.db.tenantId) @@ -806,16 +851,42 @@ export class ObjectStorage { } } -function encodeContinuationToken(name: string) { - return Buffer.from(`l:${name}`).toString('base64') +interface ContinuationToken { + startAfter: string + sortOrder?: string // 'asc' | 'desc' + sortColumn?: string + sortColumnAfter?: string } -function decodeContinuationToken(token: string) { - const decoded = Buffer.from(token, 'base64').toString().split(':') +const CONTINUATION_TOKEN_PART_MAP: Record = { + l: 'startAfter', + o: 'sortOrder', + c: 'sortColumn', + a: 'sortColumnAfter', +} - if (decoded.length === 0) { - throw new Error('Invalid continuation token') +function encodeContinuationToken(tokenInfo: ContinuationToken) { + let result = '' + for (const [k, v] of Object.entries(CONTINUATION_TOKEN_PART_MAP)) { + if (tokenInfo[v]) { + result += `${k}:${tokenInfo[v]}\n` + } } + return Buffer.from(result.slice(0, -1)).toString('base64') +} - return decoded[1] +function decodeContinuationToken(token: string): ContinuationToken { + const decodedParts = Buffer.from(token, 'base64').toString().split('\n') + const result: ContinuationToken = { + startAfter: '', + sortOrder: 'asc', + } + for (const part of decodedParts) { + const partMatch = part.match(/^(\S):(.*)/) + if (!partMatch || partMatch.length !== 3 || !(partMatch[1] in CONTINUATION_TOKEN_PART_MAP)) { + throw new Error('Invalid continuation token') + } + result[CONTINUATION_TOKEN_PART_MAP[partMatch[1]]] = partMatch[2] + } + return result } diff --git a/src/storage/protocols/iceberg/catalog/errors.ts b/src/storage/protocols/iceberg/catalog/errors.ts new file mode 100644 index 000000000..f18a8c2a7 --- /dev/null +++ b/src/storage/protocols/iceberg/catalog/errors.ts @@ -0,0 +1,288 @@ +/** + * Iceberg REST Catalog API Error Types + * Based on Apache Iceberg REST Catalog specification (iceberg.spec.yaml) + */ + +/** + * Error model matching the Iceberg spec + * Properties: message, type, code, stack (optional) + */ +export interface IcebergErrorModel { + message: string + type: IcebergErrorType + code: number + stack?: string[] +} + +/** + * All possible Iceberg error types as defined in the spec + */ +export enum IcebergErrorType { + // Client Errors (4xx) + BadRequestException = 'BadRequestException', + NotAuthorizedException = 'NotAuthorizedException', + UnsupportedOperationException = 'UnsupportedOperationException', + NoSuchNamespaceException = 'NoSuchNamespaceException', + NoSuchTableException = 'NoSuchTableException', + NoSuchViewException = 'NoSuchViewException', + NoSuchPlanIdException = 'NoSuchPlanIdException', + NoSuchPlanTaskException = 'NoSuchPlanTaskException', + AlreadyExistsException = 'AlreadyExistsException', + NamespaceNotEmptyException = 'NamespaceNotEmptyException', + UnprocessableEntityException = 'UnprocessableEntityException', + AuthenticationTimeoutException = 'AuthenticationTimeoutException', + // Server Errors (5xx) + SlowDownException = 'SlowDownException', + InternalServerError = 'InternalServerError', +} + +/** + * HTTP status codes used in Iceberg REST API errors + */ +export enum IcebergHttpStatusCode { + BadRequest = 400, + Unauthorized = 401, + Forbidden = 403, + NotFound = 404, + NotAcceptable = 406, + Conflict = 409, + UnprocessableEntity = 422, + AuthenticationTimeout = 419, + ServiceUnavailable = 503, + InternalServerError = 500, +} + +/** + * Iceberg REST Catalog API Error class + * Implements RenderableError interface for proper error handling in the application + */ +export class IcebergError extends Error { + readonly type: IcebergErrorType + readonly code: number + readonly userStatusCode: number + readonly error?: string + + constructor(message: string, type: IcebergErrorType, code: number) { + super(message) + this.name = 'IcebergError' + this.type = type + this.code = code + this.userStatusCode = code + this.error = type + Object.setPrototypeOf(this, IcebergError.prototype) + } + + /** + * Render error in the format expected by the storage API error handler + * Matches the StorageError type + */ + render() { + return { + statusCode: this.code.toString(), + code: this.type, + error: this.type, + message: this.message, + } + } + + /** + * Get the original error (if any) + */ + getOriginalError(): unknown { + return null + } + + static fromResponse(data: unknown): IcebergError { + if (!data || typeof data !== 'object') { + return new IcebergError( + 'Unknown error', + IcebergErrorType.InternalServerError, + IcebergHttpStatusCode.InternalServerError + ) + } + + const error = data as Record + const errorObj = error.error as Record | undefined + + if (!errorObj) { + return new IcebergError( + 'Unknown error', + IcebergErrorType.InternalServerError, + IcebergHttpStatusCode.InternalServerError + ) + } + + const message = String(errorObj.message || 'Unknown error') + const type = (errorObj.type as IcebergErrorType) || IcebergErrorType.InternalServerError + const code = (errorObj.code as number) || IcebergHttpStatusCode.InternalServerError + + return new IcebergError(message, type, code) + } +} + +// Specific error factory functions + +/** + * Error: The request was malformed or invalid + */ +export function createBadRequestError(message: string): IcebergError { + return new IcebergError( + message, + IcebergErrorType.BadRequestException, + IcebergHttpStatusCode.BadRequest + ) +} + +/** + * Error: Authentication failed - token expired, revoked, malformed, or invalid + */ +export function createUnauthorizedError(message: string): IcebergError { + return new IcebergError( + message, + IcebergErrorType.NotAuthorizedException, + IcebergHttpStatusCode.Unauthorized + ) +} + +/** + * Error: Forbidden - authenticated user lacks necessary permissions + */ +export function createForbiddenError(message: string): IcebergError { + return new IcebergError( + message, + IcebergErrorType.NotAuthorizedException, + IcebergHttpStatusCode.Forbidden + ) +} + +/** + * Error: Namespace does not exist + */ +export function createNoSuchNamespaceError(message: string): IcebergError { + return new IcebergError( + message, + IcebergErrorType.NoSuchNamespaceException, + IcebergHttpStatusCode.NotFound + ) +} + +/** + * Error: Table does not exist + */ +export function createNoSuchTableError(message: string): IcebergError { + return new IcebergError( + message, + IcebergErrorType.NoSuchTableException, + IcebergHttpStatusCode.NotFound + ) +} + +/** + * Error: View does not exist + */ +export function createNoSuchViewError(message: string): IcebergError { + return new IcebergError( + message, + IcebergErrorType.NoSuchViewException, + IcebergHttpStatusCode.NotFound + ) +} + +/** + * Error: Plan ID does not exist + */ +export function createNoSuchPlanIdError(message: string): IcebergError { + return new IcebergError( + message, + IcebergErrorType.NoSuchPlanIdException, + IcebergHttpStatusCode.NotFound + ) +} + +/** + * Error: Plan task does not exist + */ +export function createNoSuchPlanTaskError(message: string): IcebergError { + return new IcebergError( + message, + IcebergErrorType.NoSuchPlanTaskException, + IcebergHttpStatusCode.NotFound + ) +} + +/** + * Error: Resource already exists + */ +export function createAlreadyExistsError(message: string): IcebergError { + return new IcebergError( + message, + IcebergErrorType.AlreadyExistsException, + IcebergHttpStatusCode.Conflict + ) +} + +/** + * Error: Namespace is not empty and cannot be deleted + */ +export function createNamespaceNotEmptyError(message: string): IcebergError { + return new IcebergError( + message, + IcebergErrorType.NamespaceNotEmptyException, + IcebergHttpStatusCode.Conflict + ) +} + +/** + * Error: Unprocessable entity - e.g., duplicate keys in request body + */ +export function createUnprocessableEntityError(message: string): IcebergError { + return new IcebergError( + message, + IcebergErrorType.UnprocessableEntityException, + IcebergHttpStatusCode.UnprocessableEntity + ) +} + +/** + * Error: Server does not support this operation + */ +export function createUnsupportedOperationError(message: string): IcebergError { + return new IcebergError( + message, + IcebergErrorType.UnsupportedOperationException, + IcebergHttpStatusCode.NotAcceptable + ) +} + +/** + * Error: Authentication token has timed out + */ +export function createAuthenticationTimeoutError(message: string): IcebergError { + return new IcebergError( + message, + IcebergErrorType.AuthenticationTimeoutException, + IcebergHttpStatusCode.AuthenticationTimeout + ) +} + +/** + * Error: Service is unavailable or overloaded + */ +export function createSlowDownError(message: string): IcebergError { + return new IcebergError( + message, + IcebergErrorType.SlowDownException, + IcebergHttpStatusCode.ServiceUnavailable + ) +} + +/** + * Error: Internal server error + */ +export function createInternalServerError(message: string): IcebergError { + return new IcebergError( + message, + IcebergErrorType.InternalServerError, + IcebergHttpStatusCode.InternalServerError + ) +} diff --git a/src/storage/protocols/iceberg/catalog/index.ts b/src/storage/protocols/iceberg/catalog/index.ts index cce8f2f8d..e4091f8db 100644 --- a/src/storage/protocols/iceberg/catalog/index.ts +++ b/src/storage/protocols/iceberg/catalog/index.ts @@ -1,7 +1,7 @@ import { + BearerTokenAuth, CatalogAuthType, SignV4Auth, - TokenAuth, } from '@storage/protocols/iceberg/catalog/rest-catalog-client' import { getConfig } from '../../../../config' @@ -15,11 +15,12 @@ export function getCatalogAuthStrategy(authType: string): CatalogAuthType { if (!icebergCatalogToken) { throw new Error('Iceberg catalog token is not configured') } - return new TokenAuth({ token: icebergCatalogToken }) + return new BearerTokenAuth({ token: icebergCatalogToken }) default: throw new Error(`Unknown auth type: ${authType}`) } } -export * from './tenant-catalog' +export * from './reconciler' export * from './rest-catalog-client' +export * from './tenant-catalog' diff --git a/src/storage/protocols/iceberg/catalog/reconciler.ts b/src/storage/protocols/iceberg/catalog/reconciler.ts new file mode 100644 index 000000000..0e96bc117 --- /dev/null +++ b/src/storage/protocols/iceberg/catalog/reconciler.ts @@ -0,0 +1,269 @@ +import { multitenantKnex } from '@internal/database' +import { KnexShardStoreFactory, ShardCatalog, ShardRow } from '@internal/sharding' +import { + ListTableResponse, + RestCatalogClient, +} from '@storage/protocols/iceberg/catalog/rest-catalog-client' +import { TableIndex } from '@storage/protocols/iceberg/knex' +import { IcebergCatalog } from '@storage/schemas' + +type NamespaceWithShardInfo = TableIndex & { shard_id?: string; shard_key?: string } + +/** + * Highly experimental reconciler for iceberg catalogs + * It will try to ensure that the local database and the upstream catalog + * are in sync, by performing the following actions: + * - Deleting local tables that do not exist upstream + * - Creating local tables for upstream tables that do not exist locally + * - Deleting upstream namespaces that are empty + */ +export class IcebergCatalogReconciler { + constructor(private readonly restCatalog: RestCatalogClient) {} + + async reconcile() { + const namespaces = await multitenantKnex + .table('iceberg_namespaces') + .select( + 'iceberg_namespaces.*', + 'iceberg_tables.shard_id as shard_id', + 'iceberg_tables.shard_key as shard_key' + ) + .join('iceberg_tables', 'iceberg_namespaces.id', 'iceberg_tables.namespace_id') + .distinct('iceberg_namespaces.name') + + await this.syncOrphanTables() + await this.deleteUpstreamEmptyNamespaces(namespaces) + } + + private async syncOrphanTables() { + const sharding = new ShardCatalog(new KnexShardStoreFactory(multitenantKnex)) + const shards = await sharding.listShardByKind('iceberg-table') + + await Promise.allSettled( + shards.map(async (shard) => { + const namespaces = this.listNamespaces(shard.shard_key) + + for await (const nsBatch of namespaces) { + for (const namespace of nsBatch) { + const tables = this.listTables(namespace[0], shard.shard_key) + + for await (const tableBatch of tables) { + const tenantId = namespace[0].split('_').shift() + if (!tenantId) { + continue + } + const dbNamespaceId = namespace[0].split('_').slice(1).join('-') + if (!dbNamespaceId) { + continue + } + + // List tables in the database for this namespace and shard + const dbTables = await multitenantKnex + .table('iceberg_tables') + .whereIn( + 'name', + tableBatch.map((t) => t.name) + ) + .where('shard_key', shard.shard_key) + .join('iceberg_namespaces', 'iceberg_tables.namespace_id', 'iceberg_namespaces.id') + .where('iceberg_namespaces.id', dbNamespaceId) + .select( + 'iceberg_tables.name', + 'iceberg_namespaces.name', + 'iceberg_tables.tenant_id' + ) + + await Promise.allSettled([ + this.deleteLocalOrphanTables(shard, dbTables, tableBatch), + this.syncUpstreamOrphanTables(shard, tenantId, dbNamespaceId, dbTables, tableBatch), + ]) + } + } + } + }) + ) + } + + private async deleteLocalOrphanTables( + shard: ShardRow, + dbTables: TableIndex[], + tableBatch: ListTableResponse['identifiers'] + ) { + const tablesToDeleteInDb = dbTables.filter( + (dbt) => !tableBatch.find((t) => t.name === dbt.name) + ) + + await multitenantKnex + .table('iceberg_tables') + .whereIn( + 'name', + tablesToDeleteInDb.map((t) => t.name) + ) + .where('shard_key', shard.shard_key) + .del() + } + + private async syncUpstreamOrphanTables( + shard: ShardRow, + tenantId: string, + namespaceId: string, + dbTables: TableIndex[], + tableBatch: ListTableResponse['identifiers'] + ) { + const shardCatalog = new ShardCatalog(new KnexShardStoreFactory(multitenantKnex)) + // Find tables that are in the catalog but not in the database + const tablesMissing = tableBatch.filter((t) => !dbTables.find((dbt) => dbt.name === t.name)) + + if (tablesMissing.length === 0) { + return + } + + await multitenantKnex.transaction(async (tnx) => { + await Promise.all( + tablesMissing.map(async (table) => { + const namespaceResp = await this.restCatalog.loadNamespaceMetadata({ + warehouse: shard.shard_key, + namespace: table.namespace[0], + }) + + const tableResp = await this.restCatalog.loadTable({ + warehouse: shard.shard_key, + namespace: table.namespace[0], + table: table.name, + }) + + let catalogId = namespaceResp.properties?.['bucket-name'] as string | undefined + + if (!catalogId) { + const firstCatalog = await multitenantKnex + .table('iceberg_catalogs') + .select('name') + .where('tenant_id', tenantId) + .first() + + if (!firstCatalog) { + // There is no catalog in the user database, meaning that the only thing we can do + // is delete the table from the upstream catalog + await this.restCatalog.dropTable({ + warehouse: shard.shard_key, + namespace: table.namespace[0], + table: table.name, + }) + + // Also special case here, since the tenant has no catalog, we can free up the shard slots + await tnx.raw( + ` + WITH shard_slots AS ( + UPDATE shard_slots + SET resource_id = null, tenant_id = null + WHERE shard_id = ? AND tenant_id = ? + RETURNING shard_id, slot_no + ), + deleted_reservations AS ( + DELETE FROM shard_reservation + WHERE shard_id = ? + AND tenant_id = ? + ) + SELECT 1; + `, + [shard.id, tenantId, shard.id, tenantId] + ) + return + } + + catalogId = firstCatalog.name + } + + const sharder = shardCatalog.withTnx(tnx) + const existingShard = await sharder.findShardByResourceId({ + kind: 'iceberg-table', + tenantId, + bucketName: catalogId, + logicalName: `${namespaceId}/${table.name}`, + }) + + if (!existingShard) { + // Reserve a shard for this table + const { reservationId } = await sharder.reserve({ + kind: 'iceberg-table', + tenantId, + bucketName: catalogId, + logicalName: `${namespaceId}/${table.name}`, + shardId: shard.id, + }) + + await sharder.confirm(reservationId, { + kind: 'iceberg-table', + tenantId, + bucketName: catalogId, + logicalName: `${namespaceId}/${table.name}`, + }) + } + + await tnx.table('iceberg_tables').insert({ + name: table.name, + namespace_id: namespaceId, + location: tableResp.metadata.location, + bucket_id: catalogId, + tenant_id: tenantId, + shard_id: shard.id, + shard_key: shard.shard_key, + remote_table_id: tableResp.metadata['table-uuid'], + }) + }) + ) + }) + } + + private async deleteUpstreamEmptyNamespaces(namespaces: NamespaceWithShardInfo[]) { + await Promise.allSettled( + namespaces.map(async (namespace) => { + const namespaceName = `${namespace.tenant_id}_${namespace.id.replaceAll('-', '_')}` + + if (!namespace.shard_key) { + return + } + + const tables = await this.restCatalog.listTables({ + namespace: namespaceName, + pageSize: 1, + warehouse: namespace.shard_key, + }) + + if (tables.identifiers.length === 0) { + await this.restCatalog.dropNamespace({ + namespace: namespaceName, + warehouse: namespace.shard_key, + }) + } + }) + ) + } + + private async *listNamespaces(shardKey: string) { + let restToken: string | undefined + do { + const resp = await this.restCatalog.listNamespaces({ + warehouse: shardKey, + pageSize: 1000, + pageToken: restToken, + }) + yield resp.namespaces + restToken = resp['next-page-token'] + } while (restToken) + } + + private async *listTables(namespaceName: string, shardKey: string) { + let restToken: string | undefined + do { + const resp = await this.restCatalog.listTables({ + warehouse: shardKey, + namespace: namespaceName, + pageSize: 1000, + pageToken: restToken, + }) + yield resp.identifiers + restToken = resp['next-page-token'] + } while (restToken) + } +} diff --git a/src/storage/protocols/iceberg/catalog/rest-catalog-client.ts b/src/storage/protocols/iceberg/catalog/rest-catalog-client.ts index 47c924761..bcc5a42e4 100644 --- a/src/storage/protocols/iceberg/catalog/rest-catalog-client.ts +++ b/src/storage/protocols/iceberg/catalog/rest-catalog-client.ts @@ -1,6 +1,21 @@ -import axios, { AxiosError, AxiosInstance, InternalAxiosRequestConfig } from 'axios' -import { ErrorCode, ERRORS, StorageBackendError } from '@internal/errors' +import { ERRORS } from '@internal/errors' import { signRequest } from 'aws-sigv4-sign' +import axios, { AxiosError, AxiosInstance, InternalAxiosRequestConfig } from 'axios' +import JSONBigint from 'json-bigint' +import { + createAlreadyExistsError, + createAuthenticationTimeoutError, + createBadRequestError, + createForbiddenError, + createInternalServerError, + createNoSuchNamespaceError, + createSlowDownError, + createUnauthorizedError, + createUnprocessableEntityError, + createUnsupportedOperationError, + IcebergError, + IcebergHttpStatusCode, +} from './errors' export interface GetConfigRequest { tenantId?: string @@ -27,7 +42,7 @@ export interface ListNamespacesRequest { export interface ListNamespacesResponse { namespaces: string[][] - nextPageToken?: string + 'next-page-token'?: string } interface CatalogAuth { @@ -39,8 +54,7 @@ interface CatalogAuth { export type CatalogAuthType = CatalogAuth export interface RestCatalogClientOptions { - connectionString: string - warehouse: string + catalogUrl: string auth: CatalogAuthType } @@ -200,6 +214,7 @@ export interface ListTableRequest { } export interface ListTableResponse { + 'next-page-token'?: string | undefined identifiers: { namespace: string[] name: string @@ -268,6 +283,9 @@ export interface TableMetadata { schemas?: Schema[] /** The ID of the current schema in the `schemas` array. */ 'current-schema-id'?: number + + 'current-snapshot-id'?: number + /** The last column ID assigned (for tracking new columns). */ 'last-column-id'?: number /** All known partition specs for the table. */ @@ -329,47 +347,55 @@ export type CreateTableResponse = LoadTableResult export class RestCatalogClient { httpClient: AxiosInstance - warehouse: string auth: CatalogAuthType constructor(options: RestCatalogClientOptions) { this.httpClient = axios.create({ - baseURL: options.connectionString, + baseURL: options.catalogUrl, }) - this.warehouse = options.warehouse this.auth = options.auth this.httpClient.interceptors.request.use((req) => { return this.auth.authorize(req) }) + // request interceptor, preventing the response the default behaviour of parsing the response with JSON.parse + this.httpClient.interceptors.request.use((request) => { + request.transformRequest = [ + (data) => { + return data ? JSONBigint.stringify(data) : data + }, + ] + request.transformResponse = [(data) => data] + return request + }) + + // response interceptor parsing the response data with JSONbigint, and returning the response + this.httpClient.interceptors.response.use((response) => { + if (response.data) { + response.data = JSONBigint.parse(response.data) + } + return response + }) + this.httpClient.interceptors.response.use( // On 2xx responses, just pass through (response) => response, // On errors… (error) => { - // If there's no response, it’s a network / CORS / timeout error + // If there's no response, it's a network / CORS / timeout error if (!error.response) { throw ERRORS.InternalError(error, 'Network error') } if (error instanceof AxiosError) { - console.log('Iceberg request failed:', { - status: error.response.status, - data: error.response.data, - headers: error.response.headers, - message: error.message, - }) - // Throw your custom error - throw new StorageBackendError({ - message: error.message, - error: error.message, - httpStatusCode: error.response.status, - code: ErrorCode.IcebergError, - originalError: error, - }) + const body = error.response?.data + const jsonResponse = typeof body === 'string' ? JSONBigint.parse(body) : body + + // Parse Iceberg error response and throw appropriate error + throw this.parseIcebergError(error.response.status, jsonResponse) } throw ERRORS.InternalError(error, 'Iceberg request failed') @@ -377,6 +403,56 @@ export class RestCatalogClient { ) } + /** + * Parse HTTP response status and body to create appropriate Iceberg error + * Handles all HTTP error codes from the Iceberg REST specification + */ + private parseIcebergError(status: number, data: unknown): IcebergError { + // Try to extract error details from response body + if (data && typeof data === 'object') { + try { + // Map error types to specific error creators + return IcebergError.fromResponse(data) + } catch { + // Fall through to status code handling + } + } + + // Handle specific status codes as per Iceberg spec + return this.createErrorByStatusCode(status) + } + + /** + * Create appropriate error based on HTTP status code + */ + private createErrorByStatusCode(status: number): IcebergError { + switch (status) { + case IcebergHttpStatusCode.BadRequest: + return createBadRequestError('Bad request') + case IcebergHttpStatusCode.Unauthorized: + return createUnauthorizedError('Unauthorized') + case IcebergHttpStatusCode.Forbidden: + return createForbiddenError('Forbidden') + case IcebergHttpStatusCode.NotFound: + return createNoSuchNamespaceError('Not found') + case IcebergHttpStatusCode.NotAcceptable: + return createUnsupportedOperationError('Unsupported operation') + case IcebergHttpStatusCode.Conflict: + return createAlreadyExistsError('Conflict') + case IcebergHttpStatusCode.UnprocessableEntity: + return createUnprocessableEntityError('Unprocessable entity') + case IcebergHttpStatusCode.AuthenticationTimeout: + return createAuthenticationTimeoutError('Authentication timeout') + case IcebergHttpStatusCode.ServiceUnavailable: + return createSlowDownError('Service unavailable') + default: + if (status >= 500) { + return createInternalServerError('Internal server error') + } + return createInternalServerError(`HTTP ${status}`) + } + } + /** * Retrieves catalog configuration settings * @@ -388,7 +464,7 @@ export class RestCatalogClient { return this.httpClient .get('/config', { params: { - warehouse: this.warehouse, + warehouse: params.warehouse, }, }) .then((response) => { @@ -422,7 +498,7 @@ export class RestCatalogClient { * @returns List of namespace identifiers */ listNamespaces(params: ListNamespacesRequest) { - const warehouse = this.getWarehouse() + const warehouse = this.getEncodedWarehouse(params.warehouse) return this.httpClient .get(`${warehouse}/namespaces`, { params }) .then((response) => response.data) @@ -442,7 +518,7 @@ export class RestCatalogClient { * @returns The created namespace response */ createNamespace(params: CreateNamespaceRequest) { - const warehouse = this.getWarehouse() + const warehouse = this.getEncodedWarehouse(params.warehouse) return this.httpClient .post(`${warehouse}/namespaces`, { namespace: params.namespace, @@ -465,7 +541,7 @@ export class RestCatalogClient { * @returns The namespace metadata */ loadNamespaceMetadata(params: LoadNamespaceMetadataRequest) { - const warehouse = this.getWarehouse() + const warehouse = this.getEncodedWarehouse(params.warehouse) return this.httpClient .get(`${warehouse}/namespaces/${params.namespace}`) .then((response) => response.data) @@ -477,11 +553,8 @@ export class RestCatalogClient { }) } - getWarehouse() { - if (!this.warehouse) { - return '' - } - return '/' + encodeURIComponent(this.warehouse) + getEncodedWarehouse(warehouse: string) { + return '/' + encodeURIComponent(warehouse) } /** @@ -492,7 +565,7 @@ export class RestCatalogClient { * @returns Void response after successful deletion */ dropNamespace(params: DeleteNamespaceRequest) { - const warehouse = this.getWarehouse() + const warehouse = this.getEncodedWarehouse(params.warehouse) return this.httpClient .delete(`${warehouse}/namespaces/${params.namespace}`) .then((response) => response.data) @@ -512,7 +585,7 @@ export class RestCatalogClient { * @returns List of table identifiers */ listTables({ namespace, ...rest }: ListTableRequest) { - const warehouse = this.getWarehouse() + const warehouse = this.getEncodedWarehouse(rest.warehouse) return this.httpClient .get(`${warehouse}/namespaces/${namespace}/tables`, { @@ -535,7 +608,7 @@ export class RestCatalogClient { * @returns The created table metadata */ createTable({ namespace, ...rest }: CreateTableRequest) { - const warehouse = this.getWarehouse() + const warehouse = this.getEncodedWarehouse(rest.warehouse) return this.httpClient .post(`${warehouse}/namespaces/${namespace}/tables`, rest) .then((response) => response.data) @@ -555,14 +628,20 @@ export class RestCatalogClient { * @returns The table metadata and location */ loadTable(params: LoadTableRequest) { - const warehouse = this.getWarehouse() + const warehouse = this.getEncodedWarehouse(params.warehouse) return this.httpClient .get(`${warehouse}/namespaces/${params.namespace}/tables/${params.table}`, { params: { snapshots: params.snapshots, }, }) - .then((response) => response.data) + .then((response) => { + // console.log({ + // url: response.request., + // headers: response.request.headers, + // }) + return response.data + }) .catch((error) => { if (error instanceof AxiosError) { console.error('Error fetching configuration:', error.response?.data) @@ -579,7 +658,7 @@ export class RestCatalogClient { * @returns The updated table metadata */ updateTable(params: CommitTableRequest) { - const warehouse = this.getWarehouse() + const warehouse = this.getEncodedWarehouse(params.warehouse) return this.httpClient .post( `${warehouse}/namespaces/${params.namespace}/tables/${params.table}`, @@ -602,7 +681,7 @@ export class RestCatalogClient { * @returns Void response after successful deletion */ dropTable(params: DropTableRequest) { - const warehouse = this.getWarehouse() + const warehouse = this.getEncodedWarehouse(params.warehouse) const query: Record = {} if (params.purgeRequested) { @@ -639,7 +718,7 @@ export class RestCatalogClient { * @returns Boolean indicating if the table exists */ tableExists(params: TableExistsRequest) { - const warehouse = this.getWarehouse() + const warehouse = this.getEncodedWarehouse(params.warehouse) return this.httpClient .head(`${warehouse}/namespaces/${params.namespace}/tables/${params.table}`) .then((response) => response.data) @@ -659,7 +738,7 @@ export class RestCatalogClient { * @returns Boolean indicating if the namespace exists */ namespaceExists(params: NamespaceExistsRequest) { - const warehouse = this.getWarehouse() + const warehouse = this.getEncodedWarehouse(params.warehouse) return this.httpClient .head(`${warehouse}/namespaces/${params.namespace}`) .then((response) => response.data) @@ -681,13 +760,16 @@ export class SignV4Auth { constructor(private readonly opts: { region: string }) {} async authorize(req: InternalAxiosRequestConfig) { - const queryParams = Object.keys(req.params || {}).reduce((acc, name) => { - if (req.params[name]) { - acc[name] = req.params[name] - } + const queryParams = Object.keys(req.params || {}).reduce( + (acc, name) => { + if (req.params[name]) { + acc[name] = req.params[name] + } - return acc - }, {} as Record) + return acc + }, + {} as Record + ) const queryString = new URLSearchParams(queryParams).toString() @@ -696,7 +778,7 @@ export class SignV4Auth { { method: req.method?.toUpperCase(), headers: req.headers, - body: req.data ? JSON.stringify(req.data) : undefined, + body: req.data ? JSONBigint.stringify(req.data) : undefined, }, { service: 's3tables', @@ -704,6 +786,7 @@ export class SignV4Auth { } ) + // Keep the original code for setting headers signedReq.headers.forEach((headerValue, headerName) => { req.headers.set(headerName, headerValue as string, true) }) @@ -713,11 +796,11 @@ export class SignV4Auth { } /** - * TokenAuth class for Bearer token authentication + * BearerTokenAuth class for Bearer token authentication * This class implements the CatalogAuth interface * to add a Bearer token to the request headers. */ -export class TokenAuth { +export class BearerTokenAuth { constructor(private readonly opts: { token: string }) {} async authorize(req: InternalAxiosRequestConfig) { diff --git a/src/storage/protocols/iceberg/catalog/tenant-catalog.ts b/src/storage/protocols/iceberg/catalog/tenant-catalog.ts index fd9ae46d9..4c407f5d7 100644 --- a/src/storage/protocols/iceberg/catalog/tenant-catalog.ts +++ b/src/storage/protocols/iceberg/catalog/tenant-catalog.ts @@ -1,3 +1,8 @@ +import { ERRORS, ErrorCode, StorageBackendError } from '@internal/errors' +import { Sharder } from '@internal/sharding' +import { ICEBERG_BUCKET_RESERVED_SUFFIX } from '@storage/limits' +import { IcebergError } from '@storage/protocols/iceberg/catalog/errors' +import { Metastore } from '../knex' import { CatalogAuthType, CommitTableRequest, @@ -14,8 +19,6 @@ import { RestCatalogClient, TableExistsRequest, } from './rest-catalog-client' -import { Metastore } from '../knex' -import { ERRORS } from '@internal/errors' /** * Configuration options for the tenant-aware REST catalog client @@ -25,9 +28,9 @@ export interface RestCatalogTenantOptions { secretAccessKey?: string tenantId: string restCatalogUrl: string - warehouse: string metastore: Metastore auth: CatalogAuthType + sharding: Sharder limits: { maxCatalogsCount: number maxNamespaceCount: number @@ -59,8 +62,7 @@ export class TenantAwareRestCatalog extends RestCatalogClient { */ constructor(private readonly options: RestCatalogTenantOptions) { super({ - connectionString: options.restCatalogUrl, - warehouse: options.warehouse, + catalogUrl: options.restCatalogUrl, auth: options.auth, }) @@ -68,21 +70,30 @@ export class TenantAwareRestCatalog extends RestCatalogClient { } async getConfig(params: GetConfigRequest) { - const catalog = await this.findCatalogById({ + const catalog = await this.findCatalogByName({ tenantId: this.tenantId, - id: params.warehouse, + name: params.warehouse, }) - return super.getConfig({ - tenantId: this.tenantId, - warehouse: catalog.id, - }) + return { + defaults: { + 'write.object-storage.partitioned-paths': 'false', + 's3.delete-enabled': 'false', + 'io-impl': 'org.apache.iceberg.aws.s3.S3FileIO', + 'write.object-storage.enabled': 'true', + prefix: catalog.name, + 'rest-metrics-reporting-enabled': 'false', + }, + overrides: { + prefix: catalog.name, + }, + } } - findCatalogById(params: { tenantId: string; id: string }) { + findCatalogByName(params: { tenantId: string; name: string }) { // Find the catalog by bucket ID and tenant ID in the metastore - return this.options.metastore.findCatalogById({ - id: params.id, + return this.options.metastore.findCatalogByName({ + name: params.name, tenantId: params.tenantId, }) } @@ -95,7 +106,7 @@ export class TenantAwareRestCatalog extends RestCatalogClient { }) } - async registerCatalog(params: { bucketId: string; tenantId: string }) { + async registerCatalog(params: { bucketName: string; bucketId: string; tenantId: string }) { // Register the catalog with the Iceberg REST API return this.options.metastore.transaction(async (store) => { const catalogCount = await store.countCatalogs({ @@ -109,6 +120,7 @@ export class TenantAwareRestCatalog extends RestCatalogClient { return store.assignCatalog({ bucketId: params.bucketId, + bucketName: params.bucketName, tenantId: params.tenantId, }) }) @@ -123,56 +135,108 @@ export class TenantAwareRestCatalog extends RestCatalogClient { * @param param0 Table creation parameters * @returns The created table with modified location paths */ - async createTable({ namespace, ...rest }: CreateTableRequest) { - this.validateResourceName(rest.name) + async createTable({ namespace, ...table }: CreateTableRequest) { + this.validateResourceName(table.name) - return this.options.metastore.transaction( - async (store) => { - const catalog = await store.findCatalogById({ - tenantId: this.tenantId, - id: rest.warehouse, - }) + return this.options.metastore.transaction(async (store) => { + const catalog = await store.findCatalogByName({ + tenantId: this.tenantId, + name: table.warehouse, + }) - const dbNamespace = await store.findNamespaceByName({ - name: namespace, - tenantId: this.tenantId, - bucketId: catalog.id, - }) + const dbNamespace = await store.findNamespaceByName({ + name: namespace, + tenantId: this.tenantId, + catalogId: catalog.id, + }) - const tableCount = await store.countTables({ - tenantId: this.tenantId, - limit: this.options.limits.maxTableCount, + try { + await store.findTableByName({ + name: table.name, namespaceId: dbNamespace.id, + tenantId: this.tenantId, }) - if (tableCount >= this.options.limits.maxTableCount) { - throw ERRORS.IcebergMaximumResourceLimit(this.options.limits.maxTableCount) + throw ERRORS.ResourceAlreadyExists() + } catch (e) { + if (!(e instanceof StorageBackendError && e.code === ErrorCode.NoSuchKey)) { + throw e } + } - const namespaceName = this.getTenantNamespaceName(dbNamespace.id) + await store.lockResource('namespace', `${this.tenantId}:${dbNamespace.id}`) + const sharder = this.options.sharding.withTnx(store.getTnx()) + + const tableCount = await store.countTables({ + tenantId: this.tenantId, + limit: this.options.limits.maxTableCount, + namespaceId: dbNamespace.id, + }) + + if (tableCount >= this.options.limits.maxTableCount) { + throw ERRORS.IcebergMaximumResourceLimit(this.options.limits.maxTableCount) + } + + const namespaceName = this.getTenantNamespaceName(dbNamespace.id) + + const { shardId, reservationId, shardKey } = await sharder.reserve({ + tenantId: this.tenantId, + kind: 'iceberg-table', + logicalName: `${dbNamespace.id}/${table.name}`, + bucketName: catalog.id, + }) + + try { try { - const table = await super.createTable({ - ...rest, - namespace: namespaceName, - }) - await store.createTable({ - name: rest.name, - bucketId: catalog.id, - namespaceId: dbNamespace.id, - tenantId: this.options.tenantId, - location: table['metadata'].location as string, + await super.createNamespace({ + namespace: [namespaceName], + warehouse: shardKey, + // Note: Underline catalog doesn't support this + // properties: { + // ...(dbNamespace.metadata ? dbNamespace.metadata : {}), + // 'bucket-name': catalog.name, + // 'tenant-id': this.tenantId, + // }, }) - - return table } catch (e) { - throw e + if (e instanceof IcebergError && e.code === 409) { + // Namespace already exists, ignore + } else { + throw e + } } - }, - { - isolationLevel: 'serializable', + + const icebergTable = await super.createTable({ + ...table, + warehouse: shardKey, + namespace: namespaceName, + }) + + await store.createTable({ + name: table.name, + bucketId: catalog.id, + bucketName: catalog.name, + namespaceId: dbNamespace.id, + tenantId: this.options.tenantId, + location: icebergTable['metadata'].location as string, + shardKey, + shardId, + remoteTableId: icebergTable['metadata']['table-uuid'], + }) + + await sharder.confirm(reservationId, { + logicalName: `${dbNamespace.id}/${table.name}`, + tenantId: this.tenantId, + kind: 'iceberg-table', + bucketName: catalog.id, + }) + + return icebergTable + } catch (e) { + throw e } - ) + }) } /** @@ -185,15 +249,15 @@ export class TenantAwareRestCatalog extends RestCatalogClient { * @returns List of table identifiers in the namespace */ async listTables(params: ListTableRequest) { - const catalog = await this.findCatalogById({ + const catalog = await this.findCatalogByName({ tenantId: this.tenantId, - id: params.warehouse, + name: params.warehouse, }) const dbNamespace = await this.findNamespaceByName({ name: params.namespace, tenantId: this.tenantId, - bucketId: catalog.id, + catalogId: catalog.id, }) const tables = await this.options.metastore.listTables({ @@ -225,32 +289,35 @@ export class TenantAwareRestCatalog extends RestCatalogClient { * @returns The loaded table with modified location paths */ async loadTable(params: LoadTableRequest) { - const catalog = await this.findCatalogById({ + const catalog = await this.findCatalogByName({ tenantId: this.tenantId, - id: params.warehouse, + name: params.warehouse, }) const namespace = await this.options.metastore.findNamespaceByName({ tenantId: this.tenantId, name: params.namespace, - bucketId: catalog.id, + catalogId: catalog.id, + }) + + const dbTable = await this.options.metastore.findTableByName({ + tenantId: this.tenantId, + name: params.table, + namespaceId: namespace.id, }) const namespaceName = this.getTenantNamespaceName(namespace.id) - const [table, dbTable] = await Promise.all([ - super.loadTable({ - ...params, - namespace: namespaceName, - snapshots: 'all', - }), - this.options.metastore.findTableByName({ - tenantId: this.tenantId, - name: params.table, - }), - ]) + if (!dbTable.shard_key) { + throw ERRORS.ShardNotFound(`Table shard key not found for table ${params.table}`) + } - return table + return super.loadTable({ + ...params, + warehouse: dbTable.shard_key, + namespace: namespaceName, + snapshots: 'all', + }) } /** @@ -263,37 +330,36 @@ export class TenantAwareRestCatalog extends RestCatalogClient { * @returns The updated table with modified location paths */ async updateTable(params: CommitTableRequest) { - return this.options.metastore.transaction( - async (store) => { - const catalog = await store.findCatalogById({ - tenantId: this.tenantId, - id: params.warehouse, - }) + return this.options.metastore.transaction(async (store) => { + const catalog = await store.findCatalogByName({ + tenantId: this.tenantId, + name: params.warehouse, + }) - const namespace = await store.findNamespaceByName({ - tenantId: this.tenantId, - name: params.namespace, - bucketId: catalog.id, - }) + const namespace = await this.options.metastore.findNamespaceByName({ + tenantId: this.tenantId, + name: params.namespace, + catalogId: catalog.id, + }) - const namespaceName = this.getTenantNamespaceName(namespace.id) + const dbTable = await this.options.metastore.findTableByName({ + tenantId: this.tenantId, + name: params.table, + namespaceId: namespace.id, + }) - const table = await super.updateTable({ - ...params, - namespace: namespaceName, - }) + if (!dbTable.shard_key) { + throw ERRORS.ShardNotFound(`Table shard key not found for table ${params.table}`) + } - await store.findTableByName({ - tenantId: this.tenantId, - name: params.table, - }) + const namespaceName = this.getTenantNamespaceName(namespace.id) - return table - }, - { - isolationLevel: 'serializable', - } - ) + return await super.updateTable({ + ...params, + warehouse: dbTable.shard_key, + namespace: namespaceName, + }) + }) } /** @@ -305,16 +371,32 @@ export class TenantAwareRestCatalog extends RestCatalogClient { * @returns Boolean indicating if the table exists */ async tableExists(params: TableExistsRequest) { + const catalog = await this.findCatalogByName({ + tenantId: this.tenantId, + name: params.warehouse, + }) + const namespace = await this.options.metastore.findNamespaceByName({ tenantId: this.tenantId, name: params.namespace, - bucketId: params.warehouse, + catalogId: catalog.id, }) + const dbTable = await this.options.metastore.findTableByName({ + tenantId: this.tenantId, + name: params.table, + namespaceId: namespace.id, + }) + + if (!dbTable.shard_key) { + throw ERRORS.ShardNotFound(`Table shard key not found for table ${params.table}`) + } + const namespaceName = this.getTenantNamespaceName(namespace.id) return super.tableExists({ ...params, + warehouse: dbTable.shard_key, namespace: namespaceName, }) } @@ -328,22 +410,15 @@ export class TenantAwareRestCatalog extends RestCatalogClient { * @returns Boolean indicating if the namespace exists */ async namespaceExists(params: NamespaceExistsRequest) { - await this.findCatalogById({ + const catalog = await this.findCatalogByName({ tenantId: this.tenantId, - id: params.warehouse, + name: params.warehouse, }) - const namespace = await this.options.metastore.findNamespaceByName({ + await this.options.metastore.findNamespaceByName({ tenantId: this.tenantId, name: params.namespace, - bucketId: params.warehouse, - }) - - const namespaceName = this.getTenantNamespaceName(namespace.id) - - return super.namespaceExists({ - ...params, - namespace: namespaceName, + catalogId: catalog.id, }) } @@ -359,42 +434,33 @@ export class TenantAwareRestCatalog extends RestCatalogClient { async createNamespace(params: CreateNamespaceRequest) { this.validateResourceName(params.namespace[0]) - return this.options.metastore.transaction( - async (store) => { - const catalog = await store.findCatalogById({ - tenantId: this.tenantId, - id: params.warehouse, - }) - - const namespace = await store.assignNamespace({ - name: params.namespace[0], - bucketId: catalog.id, - tenantId: this.options.tenantId, - }) + return await this.options.metastore.transaction(async (store) => { + await store.lockResource('namespace', `${this.tenantId}:creation`) - const namespaceCount = await store.countNamespaces({ - tenantId: this.tenantId, - limit: this.options.limits.maxNamespaceCount + 1, - }) + const namespaceCount = await store.countNamespaces({ + tenantId: this.tenantId, + limit: this.options.limits.maxNamespaceCount + 1, + }) - if (namespaceCount > this.options.limits.maxNamespaceCount) { - throw ERRORS.IcebergMaximumResourceLimit(this.options.limits.maxNamespaceCount) - } + if (namespaceCount >= this.options.limits.maxNamespaceCount) { + throw ERRORS.IcebergMaximumResourceLimit(this.options.limits.maxNamespaceCount) + } - const namespaceName = this.getTenantNamespaceName(namespace.id) + const catalog = await store.findCatalogByName({ + tenantId: this.tenantId, + name: params.warehouse, + }) - const namespaceResp = await super.createNamespace({ - namespace: [namespaceName], - properties: params.properties, - warehouse: catalog.id, - }) + const namespace = await store.createNamespace({ + name: params.namespace[0], + bucketId: catalog.id, + bucketName: catalog.name, + metadata: params.properties || {}, + tenantId: this.options.tenantId, + }) - return { namespace: [namespace.name], properties: namespaceResp.properties || {} } - }, - { - isolationLevel: 'serializable', - } - ) + return { namespace: [namespace.name], properties: params.properties || {} } + }) } /** @@ -406,23 +472,21 @@ export class TenantAwareRestCatalog extends RestCatalogClient { * @returns The namespace metadata */ async loadNamespaceMetadata(params: LoadNamespaceMetadataRequest) { - await this.findCatalogById({ + const catalog = await this.findCatalogByName({ tenantId: this.tenantId, - id: params.warehouse, + name: params.warehouse, }) const namespace = await this.findNamespaceByName({ name: params.namespace, tenantId: this.tenantId, - bucketId: params.warehouse, + catalogId: catalog.id, }) - const namespaceName = this.getTenantNamespaceName(namespace.id) - - return super.loadNamespaceMetadata({ - ...params, - namespace: namespaceName, - }) + return { + namespace: [namespace.name], + properties: namespace.metadata || {}, + } } /** @@ -432,13 +496,13 @@ export class TenantAwareRestCatalog extends RestCatalogClient { * @returns List of namespace identifiers */ async listNamespaces(params: ListNamespacesRequest) { - const catalog = await this.findCatalogById({ + const catalog = await this.findCatalogByName({ tenantId: this.tenantId, - id: params.warehouse, + name: params.warehouse, }) const namespaces = await this.options.metastore.listNamespaces({ - bucketId: catalog.id, + catalogId: catalog.id, tenantId: this.tenantId, }) @@ -446,38 +510,70 @@ export class TenantAwareRestCatalog extends RestCatalogClient { } async dropTable(params: DropTableRequest) { - await this.findCatalogById({ + const catalog = await this.findCatalogByName({ tenantId: this.tenantId, - id: params.warehouse, + name: params.warehouse, }) - const namespace = await this.findNamespaceByName({ + const namespace = await this.options.metastore.findNamespaceByName({ + tenantId: this.tenantId, name: params.namespace, + catalogId: catalog.id, + }) + + const dbTable = await this.options.metastore.findTableByName({ tenantId: this.tenantId, - bucketId: params.warehouse, + name: params.table, + namespaceId: namespace.id, }) + if (!dbTable.shard_key || !dbTable.shard_id) { + throw ERRORS.ShardNotFound(`Table shard key not found for table ${params.table}`) + } + const namespaceName = this.getTenantNamespaceName(namespace.id) - return this.options.metastore.transaction( - async (store) => { - await store.dropTable({ - table: params.table, - namespace: namespace.id, - tenantId: this.tenantId, - warehouse: params.warehouse, - }) + return this.options.metastore.transaction(async (store) => { + await store.lockResource('namespace', `${this.tenantId}:${namespace.id}`) + + const sharder = this.options.sharding.withTnx(store.getTnx()) + + await store.dropTable({ + name: params.table, + namespaceId: namespace.id, + tenantId: this.tenantId, + catalogId: catalog.id, + }) + + await sharder.freeByResource(dbTable.shard_id!, { + logicalName: `${namespace.id}/${params.table}`, + tenantId: this.tenantId, + kind: 'iceberg-table', + bucketName: params.warehouse, + }) + + // Catalog call to drop the table + await super.dropTable({ + ...params, + warehouse: dbTable.shard_key!, + purgeRequested: params.purgeRequested, + namespace: namespaceName, + }) + + const tableCount = await super.listTables({ + warehouse: dbTable.shard_key!, + namespace: namespaceName, + pageSize: 1, + }) - return super.dropTable({ - ...params, - purgeRequested: params.purgeRequested, + // If no more tables exist in the namespace, delete the upstream namespace from the shard + if (tableCount.identifiers.length === 0) { + await super.dropNamespace({ namespace: namespaceName, + warehouse: dbTable.shard_key!, }) - }, - { - isolationLevel: 'serializable', } - ) + }) } /** @@ -488,36 +584,36 @@ export class TenantAwareRestCatalog extends RestCatalogClient { * @param params Delete namespace request parameters */ async dropNamespace(params: DeleteNamespaceRequest) { - const catalog = await this.findCatalogById({ + const catalog = await this.findCatalogByName({ tenantId: this.tenantId, - id: params.warehouse, + name: params.warehouse, }) const namespace = await this.findNamespaceByName({ name: params.namespace, tenantId: this.tenantId, - bucketId: catalog.id, + catalogId: catalog.id, }) - const namespaceName = this.getTenantNamespaceName(namespace.id) + return this.options.metastore.transaction(async (store) => { + await store.lockResource('namespace', `${this.tenantId}:${namespace.id}`) - return this.options.metastore.transaction( - async (store) => { - await store.dropNamespace({ - namespace: namespace.name, - bucketId: catalog.id, - tenantId: this.tenantId, - }) + const tableCount = await store.countTables({ + tenantId: this.tenantId, + namespaceId: namespace.id, + limit: 1, + }) - await super.dropNamespace({ - ...params, - namespace: namespaceName, - }) - }, - { - isolationLevel: 'serializable', + if (tableCount > 0) { + throw ERRORS.IcebergResourceNotEmpty('namespace', params.namespace) } - ) + + await store.dropNamespace({ + namespace: namespace.name, + catalogId: catalog.id, + tenantId: this.tenantId, + }) + }) } /** @@ -536,7 +632,7 @@ export class TenantAwareRestCatalog extends RestCatalogClient { * @param params Find namespace parameters including tenant ID and namespace name * @returns The namespace metadata */ - findNamespaceByName(params: { name: string; bucketId: string; tenantId: string }) { + findNamespaceByName(params: { name: string; catalogId: string; tenantId: string }) { return this.options.metastore.findNamespaceByName(params) } @@ -581,5 +677,17 @@ export class TenantAwareRestCatalog extends RestCatalogClient { message: 'Resource name must not end with the reserved suffix "--iceberg"', }) } + + if (namespace.endsWith('--s3-table')) { + throw ERRORS.InvalidParameter('namespace', { + message: 'Resource name must not end with the reserved suffix "--iceberg"', + }) + } + + if (namespace.endsWith(ICEBERG_BUCKET_RESERVED_SUFFIX)) { + throw ERRORS.InvalidParameter('namespace', { + message: `Resource name must not end with the reserved suffix "${ICEBERG_BUCKET_RESERVED_SUFFIX}"`, + }) + } } } diff --git a/src/storage/protocols/iceberg/knex.ts b/src/storage/protocols/iceberg/knex.ts index 977d72fa3..8938fbaab 100644 --- a/src/storage/protocols/iceberg/knex.ts +++ b/src/storage/protocols/iceberg/knex.ts @@ -1,67 +1,91 @@ -import { Knex } from 'knex' import { ERRORS } from '@internal/errors' -import { IcebergCatalog } from '@storage/schemas' +import { hashStringToInt } from '@internal/hashing' import { DBError } from '@storage/database' -import { DropTableRequest } from '@storage/protocols/iceberg/catalog' +import { IcebergCatalog } from '@storage/schemas' +import { Knex } from 'knex' -export interface AssignInterfaceParams { +export interface CreateNamespaceParams { name: string + bucketName: string bucketId: string tenantId?: string + metadata: Record } export interface ListNamespaceParams { tenantId?: string - bucketId: string + catalogId: string } export interface NamespaceIndex { id: string name: string - bucket_id: string + catalog_id: string + bucket_name: string tenant_id?: string + metadata?: Record } export interface Catalog { id: string + name: string tenant_id?: string } export interface TableIndex { id: string name: string - bucket_id: string + catalog_id: string + bucket_name: string namespace_id: string location: string tenant_id?: string + shard_key?: string + shard_id?: string + remote_table_id?: string } export interface DropNamespaceParams { namespace: string - bucketId: string + catalogId: string tenantId?: string } export interface CreateTableParams { name: string bucketId: string + bucketName: string namespaceId: string tenantId?: string + shardKey?: string + shardId?: string location: string + remoteTableId?: string } -export interface Metastore { - assignNamespace(params: AssignInterfaceParams): Promise +export interface Metastore { + createNamespace(params: CreateNamespaceParams): Promise listNamespaces(params: ListNamespaceParams): Promise dropNamespace(params: DropNamespaceParams): Promise + dropCatalog(params: { tenantId?: string; bucketId: string }): Promise + createTable(params: CreateTableParams): Promise - dropTable(params: DropTableRequest): Promise + dropTable(params: { + name: string + namespaceId: string + catalogId: string + tenantId: string + }): Promise findTableByLocation(params: { tenantId?: string; location: string }): Promise findTableById(params: { tenantId?: string; namespaceId: string; id: string }): Promise - findTableByName(params: { tenantId?: string; name: string }): Promise + findTableByName(params: { + tenantId?: string + name: string + namespaceId: string + }): Promise findNamespaceByName(params: { tenantId: string - bucketId: string + catalogId: string name: string }): Promise transaction( @@ -69,7 +93,7 @@ export interface Metastore { opts?: { isolationLevel?: Knex.IsolationLevels } ): Promise - assignCatalog(param: { bucketId: string; tenantId: string }): Promise + assignCatalog(param: { bucketName: string; tenantId: string }): Promise countCatalogs(params: { tenantId: string; limit: number }): Promise countNamespaces(param: { tenantId: string; limit: number }): Promise countTables(params: { namespaceId: string; tenantId?: string; limit: number }): Promise @@ -77,21 +101,88 @@ export interface Metastore { tenantId?: string limit: number }): Promise<{ namespaces: number; tables: number }> - findCatalogById(param: { tenantId: string; id: string }): Promise + findCatalogByName(param: { + tenantId: string + name: string + deleted?: boolean + }): Promise + + findCatalogById(param: { + tenantId: string + id: string + deleted?: boolean + }): Promise listTables(param: { tenantId: string pageSize: number | undefined namespaceId: string }): Promise + + lockResource(resourceType: string, resourceId: string): Promise + + getTnx(): Tnx } -export class KnexMetastore implements Metastore { +export class KnexMetastore implements Metastore { constructor( private readonly db: Knex | Knex.Transaction, private readonly ops: { schema: string; multiTenant?: boolean } ) {} + lockResource(resourceType: string, resourceId: string): Promise { + const lockId = hashStringToInt(`${resourceType}:${resourceId}`) + return this.db.raw('SELECT pg_advisory_xact_lock(?::bigint)', [lockId]).then(() => {}) + } + + getTnx() { + if (this.db.isTransaction) { + return this.db as Knex.Transaction + } + + throw new Error('Not in a transaction') + } + + async dropCatalog(params: { + tenantId?: string | undefined + bucketId: string + soft?: boolean + }): Promise { + const table = this.ops.multiTenant ? 'iceberg_catalogs' : 'buckets_analytics' + + if (params.soft) { + const query = this.db + .withSchema(this.ops.schema) + .table(table) + .andWhere('id', params.bucketId) + .update({ + deleted_at: new Date(), + }) + + if (this.ops.multiTenant) { + query.andWhere('tenant_id', params.tenantId) + } + + const n = await query + + return n > 0 + } + + const query = this.db + .withSchema(this.ops.schema) + .table(table) + .andWhere('id', params.bucketId) + .del() + + if (this.ops.multiTenant) { + query.andWhere('tenant_id', params.tenantId) + } + + const del = await query + + return del > 0 + } + listTables(param: { tenantId: string pageSize: number | undefined @@ -100,7 +191,7 @@ export class KnexMetastore implements Metastore { const query = this.db .withSchema(this.ops.schema) .table('iceberg_tables') - .select('id', 'name', 'namespace_id') + .select('id', 'name', 'namespace_id', 'shard_id', 'shard_key') .where('namespace_id', param.namespaceId) if (this.ops.multiTenant) { @@ -123,25 +214,23 @@ export class KnexMetastore implements Metastore { const countNamespaces = this.db .withSchema(this.ops.schema) .table('iceberg_namespaces') - .where('bucket_id', params.bucketId) + .where('bucket_name', params.bucketId) .limit(params.limit) .count<{ count: string }>('id as n_count') if (this.ops.multiTenant) { countNamespaces.andWhere('tenant_id', params.tenantId) - countNamespaces.select('tenant_id') } const countTables = this.db .withSchema(this.ops.schema) .table('iceberg_tables') - .where('bucket_id', params.bucketId) + .where('bucket_name', params.bucketId) .limit(params.limit) .count<{ count: string }>('id as t_count') if (this.ops.multiTenant) { countTables.andWhere('tenant_id', params.tenantId) - countTables.select('tenant_id') } const resultQuery = this.db @@ -179,13 +268,18 @@ export class KnexMetastore implements Metastore { return query.first() } - async dropTable(params: DropTableRequest): Promise { + async dropTable(params: { + name: string + namespaceId: string + catalogId: string + tenantId: string + }): Promise { const query = this.db .withSchema(this.ops.schema) .table('iceberg_tables') - .where('name', params.table) - .andWhere('namespace_id', params.namespace) - .andWhere('bucket_id', params.warehouse) + .where('name', params.name) + .andWhere('namespace_id', params.namespaceId) + .andWhere('catalog_id', params.catalogId) if (this.ops.multiTenant) { query.andWhere('tenant_id', params.tenantId) @@ -194,22 +288,33 @@ export class KnexMetastore implements Metastore { return query.del() } - async findCatalogById(param: { tenantId: string; id: string }): Promise { + async findCatalogByName(param: { + tenantId: string + name: string + deleted?: boolean + }): Promise { const table = this.ops.multiTenant ? 'iceberg_catalogs' : 'buckets_analytics' - const query = this.db.withSchema(this.ops.schema).table(table).select('id') + const query = this.db + .withSchema(this.ops.schema) + .table(table) + .select('id', 'name') + + if (!param.deleted) { + query.andWhere('deleted_at', null) + } if (this.ops.multiTenant) { query.select('tenant_id') query.andWhere('tenant_id', param.tenantId) } - query.andWhere('id', param.id) + query.andWhere('name', param.name) const result = await query.first() if (!result) { - throw ERRORS.NoSuchCatalog(param.id) + throw ERRORS.NoSuchCatalog(param.name) } return result } @@ -229,12 +334,17 @@ export class KnexMetastore implements Metastore { return parseInt(result?.count || '0', 10) } - async assignCatalog(params: { bucketId: string; tenantId: string }): Promise { + async assignCatalog(params: { + bucketName: string + bucketId: string + tenantId: string + }): Promise { const catalog: Catalog = { id: params.bucketId, + name: params.bucketName, } - const conflictColumns = ['id'] + const conflictColumns = ['name'] if (this.ops.multiTenant) { catalog['tenant_id'] = params.tenantId conflictColumns.push('tenant_id') @@ -243,14 +353,14 @@ export class KnexMetastore implements Metastore { .withSchema(this.ops.schema) .table('iceberg_catalogs') .insert(catalog) - .onConflict(conflictColumns) + .onConflict(this.db.raw(`(${conflictColumns.join(', ')}) WHERE deleted_at IS NULL`)) .merge({ updated_at: new Date(), }) .returning('*') if (result.length === 0) { - throw ERRORS.NoSuchKey(params.bucketId) + throw ERRORS.NoSuchKey(params.bucketName) } return { @@ -282,12 +392,12 @@ export class KnexMetastore implements Metastore { async findNamespaceByName(params: { tenantId: string name: string - bucketId: string + catalogId: string }): Promise { const query = this.db .withSchema(this.ops.schema) .table('iceberg_namespaces') - .select('id', 'name', 'bucket_id') + .select('id', 'name', 'bucket_name', 'metadata') if (this.ops.multiTenant) { query.select('tenant_id') @@ -295,7 +405,7 @@ export class KnexMetastore implements Metastore { } query.andWhere('name', params.name) - query.andWhere('bucket_id', params.bucketId) + query.andWhere('catalog_id', params.catalogId) const result = await query.first() @@ -305,27 +415,39 @@ export class KnexMetastore implements Metastore { return result } - dropNamespace(params: DropNamespaceParams): Promise { + async dropNamespace(params: DropNamespaceParams): Promise { const query = this.db .withSchema(this.ops.schema) .table('iceberg_namespaces') .where('name', params.namespace) - .andWhere('bucket_id', params.bucketId) + .andWhere('catalog_id', params.catalogId) if (this.ops.multiTenant) { query.andWhere('tenant_id', params.tenantId) } - return query.del() + try { + await query.del() + } catch (error) { + if ( + error instanceof Error && + (error.message.includes('RESTRICT') || error.message.includes('foreign key constraint')) + ) { + throw ERRORS.IcebergResourceNotEmpty('namespace', params.namespace) + } + throw DBError.fromError(error) + } } - async assignNamespace(params: AssignInterfaceParams) { + async createNamespace(params: CreateNamespaceParams) { const namespaceIndex: Omit = { name: params.name, - bucket_id: params.bucketId, + catalog_id: params.bucketId, + bucket_name: params.bucketName, + metadata: params.metadata, } - const conflictColumns = ['bucket_id', 'name'] + const conflictColumns = ['catalog_id', 'name'] if (this.ops.multiTenant) { namespaceIndex['tenant_id'] = params.tenantId conflictColumns.unshift('tenant_id') @@ -354,14 +476,14 @@ export class KnexMetastore implements Metastore { const query = this.db .withSchema(this.ops.schema) .table('iceberg_namespaces') - .select('name', 'bucket_id') + .select('id', 'name', 'bucket_name') if (this.ops.multiTenant) { query.select('tenant_id') query.andWhere('tenant_id', params.tenantId) } - query.andWhere('bucket_id', params.bucketId) + query.andWhere('catalog_id', params.catalogId) return query } @@ -369,12 +491,16 @@ export class KnexMetastore implements Metastore { async createTable(params: CreateTableParams) { const tableIndex: Omit = { name: params.name, - bucket_id: params.bucketId, + catalog_id: params.bucketId, + bucket_name: params.bucketName, namespace_id: params.namespaceId, location: params.location, + shard_key: params.shardKey, + shard_id: params.shardId, + remote_table_id: params.remoteTableId, } - const conflictColumns = ['name', 'namespace_id'] + const conflictColumns = ['catalog_id', 'name', 'namespace_id'] if (this.ops.multiTenant) { tableIndex['tenant_id'] = params.tenantId conflictColumns.unshift('tenant_id') @@ -402,7 +528,7 @@ export class KnexMetastore implements Metastore { const query = this.db .withSchema(this.ops.schema) .table('iceberg_tables') - .select('id', 'name', 'namespace_id', 'location') + .select('id', 'name', 'namespace_id', 'location', 'shard_key', 'shard_id') .where('namespace_id', params.namespaceId) if (this.ops.multiTenant) { @@ -421,11 +547,11 @@ export class KnexMetastore implements Metastore { return result } - async findTableByName(params: { tenantId: string; name: string }) { + async findTableByName(params: { tenantId: string; name: string; namespaceId: string }) { const query = this.db .withSchema(this.ops.schema) .table('iceberg_tables') - .select('id', 'name', 'namespace_id', 'location') + .select('id', 'name', 'namespace_id', 'location', 'shard_key', 'shard_id') if (this.ops.multiTenant) { query.select('tenant_id') @@ -433,6 +559,7 @@ export class KnexMetastore implements Metastore { } query.andWhere('name', params.name) + query.andWhere('namespace_id', params.namespaceId) const result = await query.first() @@ -473,4 +600,35 @@ export class KnexMetastore implements Metastore { const result = await query.first<{ count: string }>() return parseInt(result?.count || '0', 10) } + + async findCatalogById(param: { + id: string + tenantId: string + deleted?: boolean + }): Promise { + const table = this.ops.multiTenant ? 'iceberg_catalogs' : 'buckets_analytics' + + const query = this.db + .withSchema(this.ops.schema) + .table(table) + .select('id', 'name') + + if (this.ops.multiTenant) { + query.select('tenant_id') + query.andWhere('tenant_id', param.tenantId) + } + + query.andWhere('id', param.id) + + if (!param.deleted) { + query.andWhere('deleted_at', null) + } + + const catalog = await query.first() + + if (!catalog) { + throw ERRORS.NoSuchCatalog(param.id) + } + return catalog + } } diff --git a/src/storage/protocols/s3/byte-limit-stream.ts b/src/storage/protocols/s3/byte-limit-stream.ts index fce7c98aa..6efe14f08 100644 --- a/src/storage/protocols/s3/byte-limit-stream.ts +++ b/src/storage/protocols/s3/byte-limit-stream.ts @@ -1,5 +1,5 @@ -import { Transform, TransformCallback } from 'stream' import { ERRORS } from '@internal/errors' +import { Transform, TransformCallback } from 'stream' export class ByteLimitTransformStream extends Transform { bytesProcessed = 0 diff --git a/src/storage/protocols/s3/credentials/manager.ts b/src/storage/protocols/s3/credentials/manager.ts index caaf2c96a..2a294ad0e 100644 --- a/src/storage/protocols/s3/credentials/manager.ts +++ b/src/storage/protocols/s3/credentials/manager.ts @@ -1,22 +1,36 @@ import crypto from 'node:crypto' -import { LRUCache } from 'lru-cache' -import objectSizeOf from 'object-sizeof' -import { S3Credentials, S3CredentialsManagerStore, S3CredentialsRaw } from './store' +import { decrypt, encrypt } from '@internal/auth' +import { + createLruCache, + DEFAULT_CACHE_PURGE_STALE_INTERVAL_MS, + TENANT_S3_CREDENTIALS_CACHE_NAME, +} from '@internal/cache' import { createMutexByKey } from '@internal/concurrency' import { ERRORS } from '@internal/errors' -import { getConfig } from '../../../../config' -import { decrypt, encrypt } from '@internal/auth' import { PubSubAdapter } from '@internal/pubsub' +import objectSizeOf from 'object-sizeof' +import { getConfig } from '../../../../config' +import { S3Credentials, S3CredentialsManagerStore, S3CredentialsRaw } from './store' const TENANTS_S3_CREDENTIALS_UPDATE_CHANNEL = 'tenants_s3_credentials_update' - -const tenantS3CredentialsCache = new LRUCache({ - maxSize: 1024 * 1024 * 50, // 50MB - ttl: 1000 * 60 * 60, // 1 hour - sizeCalculation: (value) => objectSizeOf(value), - updateAgeOnGet: true, - allowStale: false, -}) +// S3 credential entries are heavier and have a lower expected working-set +// cardinality than JWT payloads, so keep a tighter entry-count guardrail. +export const TENANT_S3_CREDENTIALS_CACHE_MAX_ITEMS = 16384 +export const TENANT_S3_CREDENTIALS_CACHE_MAX_SIZE_BYTES = 1024 * 1024 * 50 // 50 MiB +export const TENANT_S3_CREDENTIALS_CACHE_TTL_MS = 1000 * 60 * 60 // 1h + +const tenantS3CredentialsCache = createLruCache( + TENANT_S3_CREDENTIALS_CACHE_NAME, + { + max: TENANT_S3_CREDENTIALS_CACHE_MAX_ITEMS, + maxSize: TENANT_S3_CREDENTIALS_CACHE_MAX_SIZE_BYTES, + ttl: TENANT_S3_CREDENTIALS_CACHE_TTL_MS, + sizeCalculation: (value) => objectSizeOf(value), + updateAgeOnGet: true, + allowStale: false, + purgeStaleIntervalMs: DEFAULT_CACHE_PURGE_STALE_INTERVAL_MS, + } +) const s3CredentialsMutex = createMutexByKey() @@ -87,14 +101,16 @@ export class S3CredentialsManager { const cacheKey = `${tenantId}:${accessKey}` const cachedCredentials = tenantS3CredentialsCache.get(cacheKey) - if (cachedCredentials) { + if (cachedCredentials !== undefined) { return cachedCredentials } return s3CredentialsMutex(cacheKey, async () => { - const cachedCredentials = tenantS3CredentialsCache.get(cacheKey) + const cachedCredentials = tenantS3CredentialsCache.get(cacheKey, { + recordMetrics: false, + }) - if (cachedCredentials) { + if (cachedCredentials !== undefined) { return cachedCredentials } diff --git a/src/storage/protocols/s3/credentials/store-knex.ts b/src/storage/protocols/s3/credentials/store-knex.ts index 1abc4caf9..11fd865f6 100644 --- a/src/storage/protocols/s3/credentials/store-knex.ts +++ b/src/storage/protocols/s3/credentials/store-knex.ts @@ -1,4 +1,5 @@ import { Knex } from 'knex' +import { getConfig } from '../../../../config' import { S3Credentials, S3CredentialsManagerStore, @@ -6,6 +7,8 @@ import { S3CredentialWithDescription, } from './store' +const { multitenantDatabaseQueryTimeout } = getConfig() + export class S3CredentialsManagerStoreKnex implements S3CredentialsManagerStore { constructor(private knex: Knex) {} @@ -19,6 +22,7 @@ export class S3CredentialsManagerStoreKnex implements S3CredentialsManagerStore secret_key: credential.secretKey, claims: JSON.stringify(credential.claims), }) + .abortOnSignal(AbortSignal.timeout(multitenantDatabaseQueryTimeout)) .returning('id') return credentials[0].id } @@ -29,6 +33,7 @@ export class S3CredentialsManagerStoreKnex implements S3CredentialsManagerStore .select('id', 'description', 'access_key', 'created_at') .where('tenant_id', tenantId) .orderBy('created_at', 'asc') + .abortOnSignal(AbortSignal.timeout(multitenantDatabaseQueryTimeout)) } getOneByAccessKey(tenantId: string, accessKey: string): Promise { @@ -38,6 +43,7 @@ export class S3CredentialsManagerStoreKnex implements S3CredentialsManagerStore .where('tenant_id', tenantId) .where('access_key', accessKey) .first() + .abortOnSignal(AbortSignal.timeout(multitenantDatabaseQueryTimeout)) } async count(tenantId: string): Promise { @@ -46,6 +52,7 @@ export class S3CredentialsManagerStoreKnex implements S3CredentialsManagerStore .count<{ count: number }>('id') .where('tenant_id', tenantId) .first() + .abortOnSignal(AbortSignal.timeout(multitenantDatabaseQueryTimeout)) return Number(data?.count || 0) } @@ -55,5 +62,6 @@ export class S3CredentialsManagerStoreKnex implements S3CredentialsManagerStore .where('tenant_id', tenantId) .where('id', credentialId) .delete() + .abortOnSignal(AbortSignal.timeout(multitenantDatabaseQueryTimeout)) } } diff --git a/src/storage/protocols/s3/s3-handler.test.ts b/src/storage/protocols/s3/s3-handler.test.ts new file mode 100644 index 000000000..c3d2dd60b --- /dev/null +++ b/src/storage/protocols/s3/s3-handler.test.ts @@ -0,0 +1,76 @@ +import { vi } from 'vitest' + +// fs-xattr is pulled in transitively through the file backend; it has no Windows build. +vi.mock('fs-xattr', () => ({ + set: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve(undefined)), +})) + +import { isValidHeader } from '@storage/protocols/s3/s3-handler' + +// Mirror the constants in s3-handler.ts +const MAX_HEADER_NAME_LENGTH = 1024 * 8 +const MAX_HEADER_VALUE_LENGTH = 1024 * 8 + +describe('isValidHeader', () => { + it('accepts a typical header name and value', () => { + expect(isValidHeader('content-type', 'application/json')).toBe(true) + }) + + it('accepts all token chars permitted by RFC7230 §3.2.6', () => { + expect(isValidHeader("!#$%&'*+-.^_`|~09AZaz", 'v')).toBe(true) + }) + + it('rejects header names containing characters outside the token set', () => { + expect(isValidHeader('bad name', 'v')).toBe(false) + expect(isValidHeader('bad:name', 'v')).toBe(false) + expect(isValidHeader('bad(name)', 'v')).toBe(false) + }) + + it('rejects an empty header name', () => { + expect(isValidHeader('', 'v')).toBe(false) + }) + + it('rejects header names exceeding the max byte length', () => { + const oversizedName = 'a'.repeat(MAX_HEADER_NAME_LENGTH + 1) + expect(isValidHeader(oversizedName, 'value')).toBe(false) + }) + + it('rejects oversized names even when all characters are otherwise valid', () => { + // Long + regex-matching still has to fail: the length check must not be bypassed. + const oversizedValid = 'a'.repeat(MAX_HEADER_NAME_LENGTH + 100) + expect(isValidHeader(oversizedValid, 'ok')).toBe(false) + }) + + it('accepts header names exactly at the max byte length', () => { + const maxName = 'a'.repeat(MAX_HEADER_NAME_LENGTH) + expect(isValidHeader(maxName, 'value')).toBe(true) + }) + + it('rejects header values containing control characters', () => { + expect(isValidHeader('x-custom', 'bad\x00value')).toBe(false) + expect(isValidHeader('x-custom', 'bad\nvalue')).toBe(false) + }) + + it('rejects header values containing CRLF (header injection)', () => { + expect(isValidHeader('x-custom', 'innocent\r\nX-Injected: 1')).toBe(false) + }) + + it('rejects header values exceeding the max byte length', () => { + const oversizedValue = 'a'.repeat(MAX_HEADER_VALUE_LENGTH + 1) + expect(isValidHeader('x-custom', oversizedValue)).toBe(false) + }) + + it('accepts header values exactly at the max byte length', () => { + const maxValue = 'a'.repeat(MAX_HEADER_VALUE_LENGTH) + expect(isValidHeader('x-custom', maxValue)).toBe(true) + }) + + it('accepts an array of values when all are valid', () => { + expect(isValidHeader('x-custom', ['one', 'two', 'three'])).toBe(true) + }) + + it('rejects an array of values when any are invalid', () => { + expect(isValidHeader('x-custom', ['ok', 'bad\x00value'])).toBe(false) + }) +}) diff --git a/src/storage/protocols/s3/s3-handler.ts b/src/storage/protocols/s3/s3-handler.ts index a6c22b898..81c210209 100644 --- a/src/storage/protocols/s3/s3-handler.ts +++ b/src/storage/protocols/s3/s3-handler.ts @@ -1,6 +1,3 @@ -import { Storage } from '../../storage' -import { getConfig } from '../../../config' -import { Uploader, validateMimeType } from '../../uploader' import { AbortMultipartUploadCommandInput, CompleteMultipartUploadCommandInput, @@ -20,18 +17,22 @@ import { UploadPartCommandInput, UploadPartCopyCommandInput, } from '@aws-sdk/client-s3' +import { decrypt, encrypt } from '@internal/auth' +import { ERRORS } from '@internal/errors' +import { logger, logSchema } from '@internal/monitoring' import { PassThrough, Readable } from 'stream' import stream from 'stream/promises' +import { getConfig } from '../../../config' import { getFileSizeLimit, mustBeValidBucketName, mustBeValidKey } from '../../limits' -import { ERRORS } from '@internal/errors' import { S3MultipartUpload } from '../../schemas' -import { decrypt, encrypt } from '@internal/auth' +import { Storage } from '../../storage' +import { Uploader, validateMimeType } from '../../uploader' import { ByteLimitTransformStream } from './byte-limit-stream' -import { logger, logSchema } from '@internal/monitoring' -import { objectMetadataCache } from '@internal/cache' const { storageS3Region, storageS3Bucket } = getConfig() +export const MAX_PART_SIZE = 5 * 1024 * 1024 * 1024 // 5GB + export class S3ProtocolHandler { constructor( protected readonly storage: Storage, @@ -165,7 +166,8 @@ export class S3ProtocolHandler { EncodingType: command.EncodingType, MaxKeys: command.MaxKeys, Prefix: command.Prefix, - ContinuationToken: command.Marker, + StartAfter: command.Marker, + cursorV1: true, }) return { @@ -173,7 +175,7 @@ export class S3ProtocolHandler { ListBucketResult: { Name: list.responseBody.ListBucketResult.Name, Prefix: list.responseBody.ListBucketResult.Prefix, - Marker: list.responseBody.ListBucketResult.ContinuationToken, + Marker: list.responseBody.ListBucketResult.NextContinuationToken, MaxKeys: list.responseBody.ListBucketResult.MaxKeys, IsTruncated: list.responseBody.ListBucketResult.IsTruncated, Contents: list.responseBody.ListBucketResult.Contents, @@ -191,7 +193,7 @@ export class S3ProtocolHandler { * * @param command */ - async listObjectsV2(command: ListObjectsV2CommandInput) { + async listObjectsV2(command: ListObjectsV2CommandInput & { cursorV1?: boolean }) { if (!command.Bucket) { throw ERRORS.MissingParameter('Bucket') } @@ -210,10 +212,10 @@ export class S3ProtocolHandler { const results = await this.storage.from(bucket).listObjectsV2({ prefix, - delimiter: delimiter, + delimiter, maxKeys: limit, cursor: continuationToken, - startAfter: startAfter, + startAfter, encodingType: command.EncodingType, }) @@ -230,7 +232,7 @@ export class S3ProtocolHandler { | Date | undefined, ETag: o.metadata?.eTag as string, - Size: o.metadata?.size as number, + Size: (o.metadata?.size as number) || 0, StorageClass: 'STANDARD' as const, })) || [] @@ -250,7 +252,11 @@ export class S3ProtocolHandler { } if (results.nextCursor) { - response.ListBucketResult.NextContinuationToken = results.nextCursor + if (command.cursorV1) { + response.ListBucketResult.NextContinuationToken = results.nextCursorKey + } else { + response.ListBucketResult.NextContinuationToken = results.nextCursor + } } return { @@ -407,6 +413,10 @@ export class S3ProtocolHandler { objectName: command.Key as string, isUpsert: true, owner: this.owner, + userMetadata: command.Metadata, + metadata: { + mimetype: command.ContentType, + }, }) const uploadId = await this.storage.backend.createMultiPartUpload( @@ -435,7 +445,8 @@ export class S3ProtocolHandler { version, signature, this.owner, - command.Metadata + command.Metadata, + { mimetype: command.ContentType } ) return { @@ -464,17 +475,19 @@ export class S3ProtocolHandler { throw ERRORS.InvalidUploadId() } + const multiPartUpload = await this.storage.db + .asSuperUser() + .findMultipartUpload(UploadId, 'id,version,user_metadata,metadata') + await uploader.canUpload({ bucketId: Bucket as string, objectName: Key as string, isUpsert: true, owner: this.owner, + userMetadata: multiPartUpload.user_metadata || undefined, + metadata: multiPartUpload.metadata || undefined, }) - const multiPartUpload = await this.storage.db - .asSuperUser() - .findMultipartUpload(UploadId, 'id,version,user_metadata') - const parts = command.MultipartUpload?.Parts || [] if (parts.length === 0) { @@ -530,10 +543,10 @@ export class S3ProtocolHandler { responseBody: { CompleteMultipartUploadResult: { Location: `${Bucket}/${Key}`, - Bucket: Bucket, - Key: Key, + Bucket, + Key, ChecksumCRC32: resp.ChecksumCRC32, - ChecksumCRC32C: resp.ChecksumCRC32, + ChecksumCRC32C: resp.ChecksumCRC32C, ChecksumSHA1: resp.ChecksumSHA1, ChecksumSHA256: resp.ChecksumSHA256, ETag: resp.ETag, @@ -572,11 +585,18 @@ export class S3ProtocolHandler { const maxFileSize = await getFileSizeLimit(this.storage.db.tenantId, bucket?.file_size_limit) const uploader = new Uploader(this.storage.backend, this.storage.db, this.storage.location) + + const multipartData = await this.storage.db + .asSuperUser() + .findMultipartUpload(UploadId, 'version,user_metadata,metadata') + await uploader.canUpload({ bucketId: Bucket as string, objectName: Key as string, owner: this.owner, isUpsert: true, + userMetadata: multipartData.user_metadata || undefined, + metadata: multipartData.metadata || undefined, }) const multipart = await this.shouldAllowPartUpload(UploadId, ContentLength, maxFileSize) @@ -675,7 +695,11 @@ export class S3ProtocolHandler { */ async putObject( command: PutObjectCommandInput, - options: { signal?: AbortSignal; isTruncated: () => boolean } + options: { + signal?: AbortSignal + isTruncated: () => boolean + declaredContentLength?: number + } ) { const uploader = new Uploader(this.storage.backend, this.storage.db, this.storage.location) @@ -688,19 +712,18 @@ export class S3ProtocolHandler { body: command.Body as Readable, cacheControl: command.CacheControl!, mimeType: command.ContentType!, + contentLength: command.ContentLength, + declaredContentLength: options.declaredContentLength, isTruncated: options.isTruncated, - userMetadata: command.Metadata, }, objectName: command.Key as string, + userMetadata: command.Metadata, owner: this.owner, isUpsert: true, uploadType: 's3', signal: options.signal, }) - // Invalidate cache entry as object was updated - objectMetadataCache.invalidate(this.tenantId, command.Bucket, command.Key) - return { headers: { etag: upload.metadata.eTag, @@ -732,7 +755,7 @@ export class S3ProtocolHandler { const multipart = await this.storage.db .asSuperUser() - .findMultipartUpload(UploadId, 'id,version') + .findMultipartUpload(UploadId, 'id,version,user_metadata,metadata') const uploader = new Uploader(this.storage.backend, this.storage.db, this.storage.location) await uploader.canUpload({ @@ -740,6 +763,8 @@ export class S3ProtocolHandler { objectName: Key, owner: this.owner, isUpsert: true, + userMetadata: multipart.user_metadata || undefined, + metadata: multipart.metadata || undefined, }) await this.storage.backend.abortMultipartUpload( @@ -766,7 +791,7 @@ export class S3ProtocolHandler { } if (!Key) { - throw ERRORS.MissingParameter('Bucket') + throw ERRORS.MissingParameter('Key') } const r = await this.storage.backend.headObject(Bucket, Key, undefined) @@ -798,7 +823,7 @@ export class S3ProtocolHandler { } if (!Key) { - throw ERRORS.MissingParameter('Bucket') + throw ERRORS.MissingParameter('Key') } const object = await this.storage @@ -875,28 +900,9 @@ export class S3ProtocolHandler { let userMetadata: Record | undefined | null if (!options?.skipDbCheck) { - // Try to get from cache first (critical for COG workloads with many range requests) - const cached = objectMetadataCache.get(this.tenantId, bucket, key) - - if (cached) { - version = cached.version - userMetadata = cached.user_metadata - } else { - // Cache miss - fetch from DB and cache it - const object = await this.storage.from(bucket).findObject(key, 'version,user_metadata') - version = object.version - userMetadata = object.user_metadata - - // Cache for subsequent requests (COG tiles will reuse this) - if (object.id && object.version) { - objectMetadataCache.set(this.tenantId, bucket, key, { - id: object.id, - version: object.version, - bucket_id: bucket, - user_metadata: object.user_metadata || undefined, - }) - } - } + const object = await this.storage.from(bucket).findObject(key, 'version,user_metadata') + version = object.version + userMetadata = object.user_metadata } const response = await this.storage.backend.getObject( @@ -921,16 +927,50 @@ export class S3ProtocolHandler { metadataHeaders = toAwsMeatadataHeaders(userMetadata) } + const headers: Record = { + 'cache-control': response.metadata.cacheControl, + 'content-length': response.metadata.contentLength?.toString() || '0', + 'content-range': response.metadata.contentRange?.toString() || '', + 'content-type': response.metadata.mimetype, + etag: response.metadata.eTag, + 'last-modified': response.metadata.lastModified?.toUTCString() || '', + ...metadataHeaders, + } + + // Handle response header overrides + if ( + command.ResponseContentDisposition && + isValidHeader('content-disposition', command.ResponseContentDisposition) + ) { + headers['content-disposition'] = command.ResponseContentDisposition + } + if (command.ResponseContentType && isValidHeader('content-type', command.ResponseContentType)) { + headers['content-type'] = command.ResponseContentType + } + if ( + command.ResponseCacheControl && + isValidHeader('cache-control', command.ResponseCacheControl) + ) { + headers['cache-control'] = command.ResponseCacheControl + } + if ( + command.ResponseContentEncoding && + isValidHeader('content-encoding', command.ResponseContentEncoding) + ) { + headers['content-encoding'] = command.ResponseContentEncoding + } + if ( + command.ResponseContentLanguage && + isValidHeader('content-language', command.ResponseContentLanguage) + ) { + headers['content-language'] = command.ResponseContentLanguage + } + if (command.ResponseExpires) { + headers['expires'] = command.ResponseExpires.toUTCString() + } + return { - headers: { - 'cache-control': response.metadata.cacheControl, - 'content-length': response.metadata.contentLength?.toString() || '0', - 'content-range': response.metadata.contentRange?.toString() || '', - 'content-type': response.metadata.mimetype, - etag: response.metadata.eTag, - 'last-modified': response.metadata.lastModified?.toUTCString() || '', - ...metadataHeaders, - }, + headers, responseBody: response.body, statusCode: command.Range ? 206 : 200, } @@ -956,9 +996,6 @@ export class S3ProtocolHandler { await this.storage.from(Bucket).deleteObject(Key) - // Invalidate cache entry for deleted object - objectMetadataCache.invalidate(this.tenantId, Bucket, Key) - return {} } @@ -992,22 +1029,25 @@ export class S3ProtocolHandler { .from(Bucket) .deleteObjects(Delete.Objects.map((o) => o.Key || '')) - // Invalidate cache entries for all deleted objects - for (const obj of deletedResult) { - objectMetadataCache.invalidate(this.tenantId, Bucket, obj.name) + const deletedNames = new Set() + for (const result of deletedResult) { + deletedNames.add(result.name) } - const deleted = Delete.Objects.filter((o) => deletedResult.find((d) => d.name === o.Key)).map( - (o) => ({ Key: o.Key }) - ) + const deleted: { Key?: string }[] = [] + const errors: { Key?: string; Code: string; Message: string }[] = [] - const errors = Delete.Objects.filter((o) => !deletedResult.find((d) => d.name === o.Key)).map( - (o) => ({ - Key: o.Key, - Code: 'AccessDenied', - Message: "You do not have permission to delete this object or the object doesn't exists", - }) - ) + for (const object of Delete.Objects) { + if (object.Key !== undefined && deletedNames.has(object.Key)) { + deleted.push({ Key: object.Key }) + } else { + errors.push({ + Key: object.Key, + Code: 'AccessDenied', + Message: "You do not have permission to delete this object or the object doesn't exist", + }) + } + } return { responseBody: { @@ -1223,13 +1263,6 @@ export class S3ProtocolHandler { const uploader = new Uploader(this.storage.backend, this.storage.db, this.storage.location) - await uploader.canUpload({ - bucketId: Bucket, - objectName: Key, - owner: this.owner, - isUpsert: true, - }) - const [destinationBucket] = await this.storage.db.asSuperUser().withTransaction(async (db) => { return Promise.all([ db.findBucketById(Bucket, 'file_size_limit'), @@ -1241,6 +1274,19 @@ export class S3ProtocolHandler { destinationBucket?.file_size_limit ) + const multipartData = await this.storage.db + .asSuperUser() + .findMultipartUpload(UploadId, 'version,user_metadata,metadata') + + await uploader.canUpload({ + bucketId: Bucket, + objectName: Key, + owner: this.owner, + isUpsert: true, + userMetadata: multipartData.user_metadata || undefined, + metadata: multipartData.metadata || undefined, + }) + const multipart = await this.shouldAllowPartUpload(UploadId, Number(copySize), maxFileSize) const uploadPart = await this.storage.backend.uploadPartCopy( @@ -1281,14 +1327,29 @@ export class S3ProtocolHandler { } parseMetadataHeaders(headers: Record): Record | undefined { - let metadata: Record | undefined = undefined + let metadata: Record | undefined + const metadataPrefix = 'x-amz-meta-' - Object.keys(headers) - .filter((key) => key.startsWith('x-amz-meta-')) - .forEach((key) => { - if (!metadata) metadata = {} - metadata[key.replace('x-amz-meta-', '')] = headers[key] as string - }) + for (const key in headers) { + if (!key.startsWith(metadataPrefix)) { + continue + } + + if (!Object.prototype.hasOwnProperty.call(headers, key)) { + continue + } + + const value = headers[key] + if (typeof value !== 'string') { + continue + } + + if (!metadata) { + metadata = {} + } + + metadata[key.slice(metadataPrefix.length)] = value + } return metadata } @@ -1314,7 +1375,7 @@ export class S3ProtocolHandler { return this.storage.db.asSuperUser().withTransaction(async (db) => { const multipart = await db.findMultipartUpload( uploadId, - 'in_progress_size,version,upload_signature', + 'in_progress_size,version,upload_signature,user_metadata,metadata', { forUpdate: true, } @@ -1351,7 +1412,7 @@ const HEADER_NAME_RE = /^[!#$%&'*+\-.\^_`|~0-9A-Za-z]+$/ const HEADER_VALUE_RE = /^[\t\x20-\x7e\x80-\xff]+$/ export function isValidHeader(name: string, value: string | string[]): boolean { - if (Buffer.from(`${name}`).byteLength < MAX_HEADER_NAME_LENGTH && !HEADER_NAME_RE.test(name)) { + if (Buffer.from(`${name}`).byteLength > MAX_HEADER_NAME_LENGTH || !HEADER_NAME_RE.test(name)) { return false } const values = Array.isArray(value) ? value : [value] @@ -1364,18 +1425,16 @@ function toAwsMeatadataHeaders(records: Record) { const metadataHeaders: Record = {} let missingCount = 0 - if (records) { - Object.keys(records).forEach((key) => { - const value = records[key] - if (!!value && isUSASCII(value as string) && isValidHeader(key, value as string)) { - metadataHeaders['x-amz-meta-' + key.toLowerCase()] = value - } else { - missingCount++ - } - }) - } + Object.keys(records).forEach((key) => { + const value = records[key] + if (value && typeof value === 'string' && isUSASCII(value) && isValidHeader(key, value)) { + metadataHeaders['x-amz-meta-' + key.toLowerCase()] = value + } else { + missingCount++ + } + }) - if (missingCount) { + if (missingCount > 0) { metadataHeaders['x-amz-missing-meta'] = missingCount } diff --git a/src/storage/protocols/s3/signature-v4-stream.test.ts b/src/storage/protocols/s3/signature-v4-stream.test.ts new file mode 100644 index 000000000..d296a9054 --- /dev/null +++ b/src/storage/protocols/s3/signature-v4-stream.test.ts @@ -0,0 +1,600 @@ +import { finished } from 'node:stream/promises' +import { EMPTY_SHA256_HASH, SignatureV4 } from '@storage/protocols/s3/signature-v4' +import { + ChunkSignatureParserOptions, + ChunkSignatureV4Parser, +} from '@storage/protocols/s3/signature-v4-stream' +import { Buffer } from 'buffer' +import crypto from 'crypto' +import { vi } from 'vitest' + +function deriveSigningKey(secretKey: string, shortDate: string, region: string, service: string) { + const dateKey = crypto.createHmac('sha256', `AWS4${secretKey}`).update(shortDate).digest() + const regionKey = crypto.createHmac('sha256', dateKey).update(region).digest() + const serviceKey = crypto.createHmac('sha256', regionKey).update(service).digest() + return crypto.createHmac('sha256', serviceKey).update('aws4_request').digest() +} + +function createChunkSignature( + payload: Buffer | string, + previousSignature: string, + options: { + longDate: string + shortDate: string + region: string + service: string + secretKey: string + } +) { + const buffer = Buffer.isBuffer(payload) ? payload : Buffer.from(payload) + const signingKey = deriveSigningKey( + options.secretKey, + options.shortDate, + options.region, + options.service + ) + const hash = crypto.createHash('sha256').update(buffer).digest('hex') + const scope = `${options.shortDate}/${options.region}/${options.service}/aws4_request` + const stringToSign = [ + 'AWS4-HMAC-SHA256-PAYLOAD', + options.longDate, + scope, + previousSignature, + EMPTY_SHA256_HASH, + hash, + ].join('\n') + + return { + hash, + signature: crypto.createHmac('sha256', signingKey).update(stringToSign).digest('hex'), + } +} + +async function expectParserError( + parser: ChunkSignatureV4Parser, + trigger: () => void, + expected: RegExp +) { + const completion = finished(parser, { readable: false }).then( + () => { + throw new Error('expected parser to emit error but it finished successfully') + }, + (err) => err as Error + ) + + trigger() + + const error = await completion + + expect(error.message).toMatch(expected) +} + +describe('ChunkSignatureV4Parser', () => { + const makeParser = (opts: Partial = {}) => { + const defaultOpts: ChunkSignatureParserOptions = { + streamingAlgorithm: 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD', + maxChunkSize: 1024, + ...opts, + } + return new ChunkSignatureV4Parser(defaultOpts) + } + + test('buffer find handles boundary-spanning delimiters after skipped prefixes', () => { + const parser = makeParser() + const queue = (parser as any).buffer + + queue.append(Buffer.from('\r')) + queue.append(Buffer.from('\n')) + queue.append(Buffer.from('\r')) + queue.append(Buffer.from('\n')) + + expect(queue.find(Buffer.from('\r\n'), 1)).toBe(2) + + queue.consume(2) + + expect(queue.find(Buffer.from('\r\n'))).toBe(0) + }) + + test('buffer consume returns the actual consumed byte count', () => { + const parser = makeParser() + const queue = (parser as any).buffer + const consumed: Buffer[] = [] + + queue.append(Buffer.from('ab')) + queue.append(Buffer.from('cd')) + + const count = queue.consume(10, (chunk: Buffer) => consumed.push(chunk)) + + expect(count).toBe(4) + expect(Buffer.concat(consumed).toString()).toBe('abcd') + expect(queue.length).toBe(0) + }) + + test('constructor throws on invalid algorithm', () => { + expect( + () => new ChunkSignatureV4Parser({ streamingAlgorithm: 'INVALID' as any, maxChunkSize: 1 }) + ).toThrow(/Invalid streaming algorithm/) + }) + + test('parseHeaderLine accepts signed header and rejects malformed signature', () => { + const parser = makeParser() + const parse = (parser as any).parseHeaderLine.bind(parser) + const validSig = 'a'.repeat(64) + const { size, signature } = parse(`5;chunk-signature=${validSig}`) + expect(size).toBe(5) + expect(signature).toBe(validSig) + expect(() => parse('5;chunk-signature=xyz')).toThrow(/Invalid chunk header/) + expect(() => parse(`5;chunk-signature=${validSig};chunk-signature=${validSig}`)).toThrow( + /Invalid chunk header/ + ) + expect(() => parse(`5;other-extension=value;chunk-signature=${validSig}`)).toThrow( + /Invalid chunk header/ + ) + }) + + test('parseHeaderLine rejects invalid chunk size', () => { + const parser = makeParser() + const parse = (parser as any).parseHeaderLine.bind(parser) + expect(() => parse('zz;chunk-signature=' + 'a'.repeat(64))).toThrow(/Invalid chunk header/) + expect(() => parse('zz')).toThrow(/Invalid chunk size/) + expect(() => parse('5zz')).toThrow(/Invalid chunk size/) + }) + + test('missing signature for signed algorithm emits error', async () => { + const parser = makeParser({ maxChunkSize: 10 }) + await expectParserError( + parser, + () => { + parser.end('5\r\n') + }, + /Missing chunk signature/ + ) + }) + + test('header exceeding maxHeaderLength emits error', async () => { + const parser = makeParser({ maxHeaderLength: 2 }) + await expectParserError( + parser, + () => { + parser.end('abc') + }, + /Header exceeds 2 bytes/ + ) + }) + + test('header exceeding maxHeaderLength still emits error after CRLF arrives', async () => { + const parser = makeParser({ maxHeaderLength: 2 }) + await expectParserError( + parser, + () => { + parser.end('abc\r\n') + }, + /Header exceeds 2 bytes/ + ) + }) + + test('header at maxHeaderLength accepts a fragmented CRLF delimiter', async () => { + const sig = 'a'.repeat(64) + const parser = makeParser({ maxHeaderLength: 82, maxChunkSize: 10 }) + const dataChunks: Buffer[] = [] + const sigEvents: Array<{ sig: string; size: number; hash: string; prev: string | undefined }> = + [] + + parser.on('data', (chunk: Buffer) => dataChunks.push(chunk)) + parser.on('signatureReadyForVerification', (sigVal, size, hash, prev) => { + sigEvents.push({ sig: sigVal, size, hash, prev }) + }) + + const endPromise = new Promise((resolve, reject) => { + parser.on('end', resolve) + parser.on('error', reject) + }) + + parser.write(`1;chunk-signature=${sig}\r`) + parser.write('\na\r') + parser.write('\n') + parser.end(`0;chunk-signature=${sig}\r\n\r\n`) + + await endPromise + + expect(Buffer.concat(dataChunks).toString()).toBe('a') + expect(sigEvents).toHaveLength(2) + expect(sigEvents[0].sig).toBe(sig) + expect(sigEvents[1]).toEqual({ + sig, + size: 0, + hash: EMPTY_SHA256_HASH, + prev: sig, + }) + }) + + test('terminal chunk still requires the trailing CRLF after its header', async () => { + const sig = '0'.repeat(64) + const parser = makeParser({ maxChunkSize: 10 }) + await expectParserError( + parser, + () => { + parser.write(`1;chunk-signature=${sig}\r\n`) + parser.write('a\r\n') + parser.end(`0;chunk-signature=${sig}\r\n`) + }, + /Missing CRLF after chunk data/ + ) + }) + + test('errors when the stream ends before the terminal zero-length chunk', async () => { + const sig = '0'.repeat(64) + const parser = makeParser({ maxChunkSize: 10 }) + await expectParserError( + parser, + () => { + parser.write(`1;chunk-signature=${sig}\r\n`) + parser.end('a\r\n') + }, + /Missing final chunk/ + ) + }) + + test('errors when a signed stream ends without any chunks', async () => { + const parser = makeParser({ maxChunkSize: 10 }) + await expectParserError( + parser, + () => { + parser.end('') + }, + /Missing final chunk/ + ) + }) + + test('chunk size exceeds maxChunkSize emits error', async () => { + const parser = makeParser({ maxChunkSize: 1 }) + const sig = 'f'.repeat(64) + await expectParserError( + parser, + () => { + parser.end(`2;chunk-signature=${sig}\r\n`) + }, + /^The chunk exceeded 1 bytes$/ + ) + }) + + test('missing CRLF after chunk data emits error', async () => { + const sig = '0'.repeat(64) + const parser = makeParser({ maxChunkSize: 10 }) + await expectParserError( + parser, + () => { + parser.write(`1;chunk-signature=${sig}\r\n`) + parser.write('a') + // write invalid footer prefix to trigger error + parser.end('xx') + }, + /Missing CRLF after chunk data/ + ) + }) + + test('emits signatureReadyForVerification and data for single signed chunk', async () => { + const sig = '0'.repeat(64) + const parser = makeParser({ maxChunkSize: 10 }) + const dataChunks: Buffer[] = [] + const sigEvents: Array<{ sig: string; size: number; hash: string; prev: string | undefined }> = + [] + + parser.on('data', (chunk: Buffer) => dataChunks.push(chunk)) + parser.on('signatureReadyForVerification', (sigVal, size, hash, prev) => { + sigEvents.push({ sig: sigVal, size, hash, prev }) + }) + + const header = `5;chunk-signature=${sig}\r\n` + const payload = 'hello' + const footer = '\r\n' + const endChunk = `0;chunk-signature=${sig}\r\n\r\n` + const endPromise = new Promise((resolve, reject) => { + parser.on('end', resolve) + parser.on('error', reject) + }) + + parser.end(header + payload + footer + endChunk) + + await endPromise + + expect(Buffer.concat(dataChunks).toString()).toBe('hello') + expect(sigEvents).toHaveLength(2) + expect(sigEvents[0].size).toBe(5) + const expectedHash = crypto.createHash('sha256').update(payload).digest('hex') + expect(sigEvents[0].hash).toBe(expectedHash) + expect(sigEvents[0].sig).toBe(sig) + expect(sigEvents[0].prev).toBeUndefined() + expect(sigEvents[1]).toEqual({ + sig, + size: 0, + hash: EMPTY_SHA256_HASH, + prev: sig, + }) + }) + + test('emits signatureReadyForVerification for the zero-length terminal signed chunk', async () => { + const options = { + longDate: '20260406T120000Z', + shortDate: '20260406', + region: 'us-east-1', + service: 's3', + secretKey: 'secret-key', + } + const initialSignature = '0'.repeat(64) + const parser = makeParser({ maxChunkSize: 10 }) + const dataChunks: Buffer[] = [] + const sigEvents: Array<{ sig: string; size: number; hash: string; prev: string | undefined }> = + [] + const firstChunk = createChunkSignature(Buffer.from('hello'), initialSignature, options) + const terminalChunk = createChunkSignature(Buffer.alloc(0), firstChunk.signature, options) + + parser.on('data', (chunk: Buffer) => dataChunks.push(chunk)) + parser.on('signatureReadyForVerification', (sigVal, size, hash, prev) => { + sigEvents.push({ sig: sigVal, size, hash, prev }) + }) + + const endPromise = new Promise((resolve, reject) => { + parser.on('end', resolve) + parser.on('error', reject) + }) + + parser.end( + `5;chunk-signature=${firstChunk.signature}\r\nhello\r\n0;chunk-signature=${terminalChunk.signature}\r\n\r\n` + ) + + await endPromise + + expect(Buffer.concat(dataChunks).toString()).toBe('hello') + expect(sigEvents).toHaveLength(2) + expect(sigEvents[1]).toEqual({ + sig: terminalChunk.signature, + size: 0, + hash: EMPTY_SHA256_HASH, + prev: firstChunk.signature, + }) + }) + + test('parses fragmented signed chunks without reassembling the unread payload', async () => { + const sig = '1'.repeat(64) + const parser = makeParser({ maxChunkSize: 10 }) + const dataChunks: Buffer[] = [] + const sigEvents: Array<{ sig: string; size: number; hash: string; prev: string | undefined }> = + [] + + parser.on('data', (chunk: Buffer) => dataChunks.push(chunk)) + parser.on('signatureReadyForVerification', (sigVal, size, hash, prev) => { + sigEvents.push({ sig: sigVal, size, hash, prev }) + }) + + const endPromise = new Promise((resolve, reject) => { + parser.on('end', resolve) + parser.on('error', reject) + }) + + parser.write(`5;chunk-signature=${sig}\r`) + parser.write('\nhe') + parser.write(Buffer.from('ll')) + parser.write('o') + parser.write('\r') + parser.write('\n0;chunk-signature=') + parser.write(sig.slice(0, 20)) + parser.write(sig.slice(20)) + parser.write('\r') + parser.write('\n') + parser.end('\r\n') + + await endPromise + + expect(Buffer.concat(dataChunks).toString()).toBe('hello') + expect(sigEvents).toHaveLength(2) + expect(sigEvents[0].size).toBe(5) + expect(sigEvents[0].hash).toBe(crypto.createHash('sha256').update('hello').digest('hex')) + expect(sigEvents[0].sig).toBe(sig) + expect(sigEvents[1]).toEqual({ + sig, + size: 0, + hash: EMPTY_SHA256_HASH, + prev: sig, + }) + }) + + test('signed trailer mode verifies the zero-length terminal chunk before trailers', async () => { + const trailerKey = 'x-amz-meta-foo' + const options = { + longDate: '20260406T120000Z', + shortDate: '20260406', + region: 'us-east-1', + service: 's3', + secretKey: 'secret-key', + } + const initialSignature = '1'.repeat(64) + const parser = new ChunkSignatureV4Parser({ + streamingAlgorithm: 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER', + maxChunkSize: 1024, + trailerHeaderNames: [trailerKey], + }) + const dataChunks: Buffer[] = [] + const sigEvents: Array<{ sig: string; size: number; hash: string; prev: string | undefined }> = + [] + let trailerObj: Record | undefined + const firstChunk = createChunkSignature(Buffer.from('hello'), initialSignature, options) + const terminalChunk = createChunkSignature(Buffer.alloc(0), firstChunk.signature, options) + + parser.on('data', (chunk: Buffer) => dataChunks.push(chunk)) + parser.on('signatureReadyForVerification', (sigVal, size, hash, prev) => { + sigEvents.push({ sig: sigVal, size, hash, prev }) + }) + parser.on('trailer', (trailer) => { + trailerObj = trailer + }) + + const endPromise = new Promise((resolve, reject) => { + parser.on('end', resolve) + parser.on('error', reject) + }) + + parser.end( + `5;chunk-signature=${firstChunk.signature}\r\nhello\r\n0;chunk-signature=${terminalChunk.signature}\r\n${trailerKey}: bar\r\n\r\n` + ) + + await endPromise + + expect(Buffer.concat(dataChunks).toString()).toBe('hello') + expect(sigEvents).toHaveLength(2) + expect(sigEvents[1]).toEqual({ + sig: terminalChunk.signature, + size: 0, + hash: EMPTY_SHA256_HASH, + prev: firstChunk.signature, + }) + expect(trailerObj).toEqual({ [trailerKey]: 'bar' }) + }) + + test('supports unsigned payload algorithm and emits trailer', () => { + const trailerKey = 'x-amz-meta-foo' + const trailerValue = 'bar' + const opts: ChunkSignatureParserOptions = { + streamingAlgorithm: 'STREAMING-UNSIGNED-PAYLOAD-TRAILER', + maxChunkSize: 1024, + trailerHeaderNames: [trailerKey], + } + const parser = new ChunkSignatureV4Parser(opts) + const dataChunks: Buffer[] = [] + let trailerObj: Record | undefined + + parser.on('data', (chunk: Buffer) => dataChunks.push(chunk)) + parser.on('trailer', (t) => { + trailerObj = t + }) + + const header = `3\r\n` + const payload = 'hey' + const footer = '\r\n' + const endChunk = `0\r\n` + const trailerBlock = `${trailerKey}: ${trailerValue}\r\nother: ignore\r\n\r\n` + + parser.end(header + payload + footer + endChunk + trailerBlock) + + return new Promise((resolve) => { + parser.on('end', () => { + expect(Buffer.concat(dataChunks).toString()).toBe('hey') + expect(trailerObj).toEqual({ [trailerKey]: trailerValue }) + resolve() + }) + }) + }) + + test('supports fragmented unsigned trailers when the terminator spans writes', async () => { + const trailerKey = 'x-amz-meta-foo' + const parser = new ChunkSignatureV4Parser({ + streamingAlgorithm: 'STREAMING-UNSIGNED-PAYLOAD-TRAILER', + maxChunkSize: 1024, + trailerHeaderNames: [trailerKey], + }) + const dataChunks: Buffer[] = [] + let trailerObj: Record | undefined + + parser.on('data', (chunk: Buffer) => dataChunks.push(chunk)) + parser.on('trailer', (trailer) => { + trailerObj = trailer + }) + + const endPromise = new Promise((resolve, reject) => { + parser.on('end', resolve) + parser.on('error', reject) + }) + + parser.write('3\r\n') + parser.write('hey\r\n0\r\n') + parser.write(`${trailerKey}: bar\r\nother: ignore\r`) + parser.write('\n\r') + parser.end('\n') + + await endPromise + + expect(Buffer.concat(dataChunks).toString()).toBe('hey') + expect(trailerObj).toEqual({ [trailerKey]: 'bar' }) + }) + + test('dataRead exceeding maxChunkSize emits error', async () => { + const sig = 'a'.repeat(64) + const parser = makeParser({ maxChunkSize: 1 }) + await expectParserError( + parser, + () => { + parser.write(`2;chunk-signature=${sig}\r\n`) + parser.end('ab') + }, + /^The chunk exceeded 1 bytes$/ + ) + }) + + test('honors custom signaturePattern', () => { + const parser = makeParser({ + streamingAlgorithm: 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD', + maxChunkSize: 10, + signaturePattern: /^[0-9]+$/, + }) + const parse = (parser as any).parseHeaderLine.bind(parser) + expect(parse('3;chunk-signature=123').signature).toBe('123') + expect(() => parse('3;chunk-signature=abc')).toThrow() + }) + + test('reuses the derived signing key across chunk validations in the same scope', () => { + const secretKey = 'secret-key' + const signer = new SignatureV4({ + enforceRegion: false, + credentials: { + accessKey: 'access-key', + secretKey, + region: 'us-east-1', + service: 's3', + }, + }) + const clientSignature = { + credentials: { + accessKey: 'access-key', + shortDate: '20260406', + region: 'us-east-1', + service: 's3', + }, + signature: 'f'.repeat(64), + signedHeaders: ['host'], + longDate: '20260406T120000Z', + } + const signingKeySpy = vi.spyOn(signer as any, 'signingKey') + const firstChunk = createChunkSignature(Buffer.from('hello'), clientSignature.signature, { + longDate: clientSignature.longDate, + shortDate: clientSignature.credentials.shortDate, + region: clientSignature.credentials.region, + service: clientSignature.credentials.service, + secretKey, + }) + const secondChunk = createChunkSignature(Buffer.from('world'), firstChunk.signature, { + longDate: clientSignature.longDate, + shortDate: clientSignature.credentials.shortDate, + region: clientSignature.credentials.region, + service: clientSignature.credentials.service, + secretKey, + }) + + expect( + signer.validateChunkSignature( + clientSignature, + firstChunk.hash, + firstChunk.signature, + clientSignature.signature + ) + ).toBe(true) + expect( + signer.validateChunkSignature( + clientSignature, + secondChunk.hash, + secondChunk.signature, + firstChunk.signature + ) + ).toBe(true) + expect(signingKeySpy).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/storage/protocols/s3/signature-v4-stream.ts b/src/storage/protocols/s3/signature-v4-stream.ts index ba2f48333..1ef5f427a 100644 --- a/src/storage/protocols/s3/signature-v4-stream.ts +++ b/src/storage/protocols/s3/signature-v4-stream.ts @@ -1,9 +1,197 @@ // ChunkSignatureParser.ts -import { Transform, TransformCallback, TransformOptions } from 'stream' + +import { ERRORS } from '@internal/errors' +import { EMPTY_SHA256_HASH } from '@storage/protocols/s3/signature-v4' import crypto from 'crypto' +import { Transform, TransformCallback, TransformOptions } from 'stream' type ParserState = 'HEADER' | 'DATA' | 'FOOTER' | 'TRAILER' +const EMPTY_BUFFER = Buffer.alloc(0) as Buffer +const CRLF = Buffer.from('\r\n') +const TRAILER_TERMINATOR = Buffer.from('\r\n\r\n') +const CHUNK_SIZE_PATTERN = /^[0-9a-fA-F]+$/ + +class SegmentedBufferQueue { + private segments: Buffer[] = [] + private headIndex = 0 + private headOffset = 0 + private totalLength = 0 + + get length() { + return this.totalLength + } + + append(buffer: Buffer) { + if (buffer.length === 0) { + return + } + + this.segments.push(buffer) + this.totalLength += buffer.length + } + + peek(length: number): Buffer { + if (length === 0) { + return EMPTY_BUFFER + } + + if (length > this.totalLength) { + throw new Error('Insufficient buffered data') + } + + const head = this.segments[this.headIndex] + const availableInHead = head.length - this.headOffset + + if (length <= availableInHead) { + return head.subarray(this.headOffset, this.headOffset + length) + } + + const parts: Buffer[] = [] + let remaining = length + + for (let idx = this.headIndex; idx < this.segments.length && remaining > 0; idx++) { + const segment = this.segments[idx] + const start = idx === this.headIndex ? this.headOffset : 0 + const take = Math.min(remaining, segment.length - start) + + if (take > 0) { + parts.push(segment.subarray(start, start + take)) + remaining -= take + } + } + + return Buffer.concat(parts, length) + } + + readUtf8(length: number) { + return this.peek(length).toString('utf8') + } + + peekLastByte(): number | undefined { + if (this.totalLength === 0) { + return undefined + } + + for (let idx = this.segments.length - 1; idx >= this.headIndex; idx--) { + const segment = this.segments[idx] + const start = idx === this.headIndex ? this.headOffset : 0 + + if (segment.length > start) { + return segment[segment.length - 1] + } + } + + return undefined + } + + consume(length: number, onChunk?: (chunk: Buffer) => void): number { + const requested = Math.min(length, this.totalLength) + let remaining = requested + + while (remaining > 0 && this.headIndex < this.segments.length) { + const head = this.segments[this.headIndex] + const available = head.length - this.headOffset + const take = Math.min(remaining, available) + const piece = head.subarray(this.headOffset, this.headOffset + take) + + onChunk?.(piece) + + this.headOffset += take + this.totalLength -= take + remaining -= take + + if (this.headOffset === head.length) { + this.headIndex += 1 + this.headOffset = 0 + } + } + + this.compact() + return requested - remaining + } + + find(pattern: Buffer, fromOffset = 0): number { + if (pattern.length === 0) { + return 0 + } + + if (fromOffset >= this.totalLength) { + return -1 + } + + let absoluteOffset = 0 + let tail: Buffer = EMPTY_BUFFER + + for (let idx = this.headIndex; idx < this.segments.length; idx++) { + const segment = this.segments[idx] + const segmentStart = idx === this.headIndex ? this.headOffset : 0 + const segmentLength = segment.length - segmentStart + + if (segmentLength === 0) { + continue + } + + if (fromOffset >= absoluteOffset + segmentLength) { + absoluteOffset += segmentLength + continue + } + + const viewStart = segmentStart + Math.max(0, fromOffset - absoluteOffset) + const view = segment.subarray(viewStart) + + if (tail.length > 0) { + const boundaryLength = Math.min(pattern.length - 1, view.length) + + if (boundaryLength > 0) { + const boundary = Buffer.concat( + [tail, view.subarray(0, boundaryLength)], + tail.length + boundaryLength + ) + const boundaryIdx = boundary.indexOf(pattern) + + if (boundaryIdx >= 0) { + const matchStart = absoluteOffset - tail.length + boundaryIdx + if (matchStart >= fromOffset) { + return matchStart + } + } + } + } + + const innerIdx = view.indexOf(pattern) + if (innerIdx >= 0) { + return absoluteOffset + (viewStart - segmentStart) + innerIdx + } + + const viewTail = view.subarray(Math.max(0, view.length - (pattern.length - 1))) + const tailSource = + tail.length === 0 + ? viewTail + : Buffer.concat([tail, viewTail], tail.length + viewTail.length) + const tailLength = Math.min(pattern.length - 1, tailSource.length) + tail = tailLength === 0 ? EMPTY_BUFFER : tailSource.subarray(tailSource.length - tailLength) + absoluteOffset += segmentLength + } + + return -1 + } + + private compact() { + if (this.totalLength === 0) { + this.segments = [] + this.headIndex = 0 + this.headOffset = 0 + return + } + + if (this.headIndex > 32 && this.headIndex * 2 >= this.segments.length) { + this.segments = this.segments.slice(this.headIndex) + this.headIndex = 0 + } + } +} + /** * Represents the different types of V4 streaming algorithms supported. * @@ -67,15 +255,18 @@ export interface ChunkSignatureParserOptions extends TransformOptions { * */ export class ChunkSignatureV4Parser extends Transform { - private buffer = Buffer.alloc(0) + private readonly buffer = new SegmentedBufferQueue() private state: ParserState = 'HEADER' private bytesRemaining = 0 + private headerSearchOffset = 0 + private trailerSearchOffset = 0 private dataRead = 0 private currentSignature?: string private currentChunkSize = 0 - private currentHash!: crypto.Hash + private currentHash?: crypto.Hash private previousSignature?: string + private completedFinalChunk = false private readonly maxHeaderLength: number private readonly signaturePattern: RegExp @@ -92,7 +283,7 @@ export class ChunkSignatureV4Parser extends Transform { _transform(chunk: Buffer | string, encoding: BufferEncoding, cb: TransformCallback) { const data = typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk - this.buffer = Buffer.concat([this.buffer, data]) + this.buffer.append(data) try { let madeProgress: boolean @@ -117,7 +308,39 @@ export class ChunkSignatureV4Parser extends Transform { cb() } catch (err) { - cb(err as Error) + const error = err as Error + // Convert chunk size errors to 400 instead of 500 + if (error.message && error.message.includes('Chunk size exceeds')) { + const limit = error.message.replace('Chunk size exceeds', '').trim() + cb(ERRORS.EntityTooLarge(error, 'chunk', limit)) + } else { + cb(error) + } + } + } + + _flush(callback: TransformCallback) { + try { + switch (this.state) { + case 'HEADER': + if (this.buffer.length > 0) { + throw new Error('Incomplete chunk header') + } + if (!this.completedFinalChunk) { + throw new Error('Missing final chunk') + } + break + case 'DATA': + throw new Error('Unexpected end of chunk data') + case 'FOOTER': + throw new Error('Missing CRLF after chunk data') + case 'TRAILER': + throw new Error('Incomplete trailer section') + } + + callback() + } catch (err) { + callback(err as Error) } } @@ -140,24 +363,27 @@ export class ChunkSignatureV4Parser extends Transform { let size: number let sig: string | undefined - if (line.includes(delim)) { - const [sizeHex, signature] = line.split(delim) + const signedHeaderParts = line.split(delim) + if (signedHeaderParts.length === 2) { + const [sizeHex, signature] = signedHeaderParts + if (!CHUNK_SIZE_PATTERN.test(sizeHex) || !this.signaturePattern.test(signature)) { + throw new Error('Invalid chunk header') + } size = parseInt(sizeHex, 16) sig = signature - if (isNaN(size) || !this.signaturePattern.test(sig)) { - throw new Error(`Invalid header: "${line}"`) - } + } else if (signedHeaderParts.length > 2) { + throw new Error('Invalid chunk header') } else { - size = parseInt(line, 16) - if (isNaN(size)) { - throw new Error(`Invalid chunk size: "${line}"`) + if (!CHUNK_SIZE_PATTERN.test(line)) { + throw new Error('Invalid chunk size') } + size = parseInt(line, 16) // signature required for signed algorithms if ( this.alg === 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD' || this.alg === 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER' ) { - throw new Error(`Missing chunk signature: "${line}"`) + throw new Error('Missing chunk signature') } } return { size, signature: sig } @@ -166,14 +392,26 @@ export class ChunkSignatureV4Parser extends Transform { private consumeHeader(): boolean { if (this.buffer.length === 0) return false - const idx = this.buffer.indexOf('\r\n') + if (this.completedFinalChunk) { + throw new Error('Unexpected data after final chunk') + } + + const idx = this.buffer.find(CRLF, this.headerSearchOffset) if (idx < 0) { - if (this.buffer.length > this.maxHeaderLength) { + const effectiveHeaderLength = + this.buffer.length - (this.buffer.peekLastByte() === CRLF[0] ? 1 : 0) + if (effectiveHeaderLength > this.maxHeaderLength) { throw new Error(`Header exceeds ${this.maxHeaderLength} bytes`) } + this.headerSearchOffset = Math.max(0, this.buffer.length - (CRLF.length - 1)) return false } - const line = this.buffer.subarray(0, idx).toString('utf8') + + if (idx > this.maxHeaderLength) { + throw new Error(`Header exceeds ${this.maxHeaderLength} bytes`) + } + + const line = this.buffer.readUtf8(idx) const { size, signature } = this.parseHeaderLine(line) if (size > this.opts.maxChunkSize) { @@ -183,32 +421,43 @@ export class ChunkSignatureV4Parser extends Transform { this.currentChunkSize = size this.currentSignature = signature this.bytesRemaining = size - this.currentHash = crypto.createHash('sha256') + this.currentHash = + size > 0 && this.requiresChunkHash() ? crypto.createHash('sha256') : undefined - this.buffer = this.buffer.subarray(idx + 2) + this.buffer.consume(idx + CRLF.length) + this.headerSearchOffset = 0 this.state = 'DATA' return true } private consumeData(): boolean { const want = this.bytesRemaining - const piece = this.buffer.length <= want ? this.buffer : this.buffer.subarray(0, want) - this.currentHash.update(piece) - - if (piece.length === 0) { - const isLastChunk = this.currentChunkSize === 0 - if (isLastChunk) { + if (want === 0) { + if (this.hasTrailer()) { + this.emitCurrentSignatureForVerification() this.state = 'TRAILER' + this.trailerSearchOffset = 0 + } else { + this.dataRead = 0 + this.state = 'FOOTER' } - return isLastChunk + return true } - this.push(piece) + if (this.buffer.length === 0) { + return false + } + + const toConsume = Math.min(this.buffer.length, want) + + this.buffer.consume(toConsume, (piece) => { + this.currentHash?.update(piece) + this.push(piece) + }) - this.buffer = this.buffer.subarray(piece.length) - this.bytesRemaining -= piece.length - this.dataRead += piece.length + this.bytesRemaining -= toConsume + this.dataRead += toConsume if (this.dataRead > this.opts.maxChunkSize) { throw new Error(`Chunk size exceeds ${this.opts.maxChunkSize} bytes`) @@ -222,33 +471,34 @@ export class ChunkSignatureV4Parser extends Transform { } private consumeFooter(): boolean { - if (this.buffer.length < 2) return false - if (this.buffer[0] !== 0x0d || this.buffer[1] !== 0x0a) { + if (this.buffer.length < CRLF.length) return false + + const footer = this.buffer.peek(CRLF.length) + if (footer[0] !== 0x0d || footer[1] !== 0x0a) { throw new Error('Missing CRLF after chunk data') } - const sig = this.currentSignature - const size = this.currentChunkSize - const prev = this.previousSignature - - if (this.alg !== 'STREAMING-UNSIGNED-PAYLOAD-TRAILER') { - const hash = this.currentHash.digest('hex') - this.emit('signatureReadyForVerification', sig, size, hash, prev) + this.emitCurrentSignatureForVerification() + this.buffer.consume(CRLF.length) + if (this.currentChunkSize === 0) { + this.completedFinalChunk = true } - - this.previousSignature = sig - this.buffer = this.buffer.subarray(2) - this.state = size === 0 && this.hasTrailer() ? 'TRAILER' : 'HEADER' + this.state = 'HEADER' + this.headerSearchOffset = 0 + this.trailerSearchOffset = 0 return true } private consumeTrailer(): boolean { - const dbl = this.buffer.indexOf('\r\n\r\n') - if (dbl < 0) return false + const dbl = this.buffer.find(TRAILER_TERMINATOR, this.trailerSearchOffset) + if (dbl < 0) { + this.trailerSearchOffset = Math.max(0, this.buffer.length - (TRAILER_TERMINATOR.length - 1)) + return false + } - const block = this.buffer.subarray(0, dbl).toString('utf8') - this.buffer = this.buffer.subarray(dbl + 4) + const block = this.buffer.readUtf8(dbl) + this.buffer.consume(dbl + TRAILER_TERMINATOR.length) const parsed: Record = {} block.split('\r\n').forEach((line) => { @@ -264,7 +514,10 @@ export class ChunkSignatureV4Parser extends Transform { }) this.emit('trailer', trailers) + this.completedFinalChunk = true this.state = 'HEADER' + this.headerSearchOffset = 0 + this.trailerSearchOffset = 0 return true } @@ -272,4 +525,22 @@ export class ChunkSignatureV4Parser extends Transform { private hasTrailer(): boolean { return this.alg.endsWith('-TRAILER') } + + private requiresChunkHash() { + return this.alg !== 'STREAMING-UNSIGNED-PAYLOAD-TRAILER' + } + + private emitCurrentSignatureForVerification() { + const sig = this.currentSignature + const size = this.currentChunkSize + const prev = this.previousSignature + const hash = + this.currentHash?.digest('hex') ?? + (size === 0 && this.requiresChunkHash() ? EMPTY_SHA256_HASH : undefined) + + if (hash !== undefined) { + this.emit('signatureReadyForVerification', sig, size, hash, prev) + this.previousSignature = sig + } + } } diff --git a/src/storage/protocols/s3/signature-v4.ts b/src/storage/protocols/s3/signature-v4.ts index 4bd689eb6..89a7e90de 100644 --- a/src/storage/protocols/s3/signature-v4.ts +++ b/src/storage/protocols/s3/signature-v4.ts @@ -1,10 +1,22 @@ -import crypto from 'crypto' +import { createHash } from 'node:crypto' +import { Writable } from 'node:stream' import { ERRORS } from '@internal/errors' +import crypto from 'crypto' +import { Readable } from 'stream' +import { pipeline } from 'stream/promises' + +export enum SignatureV4Service { + S3 = 's3', + S3VECTORS = 's3vectors', +} interface SignatureV4Options { enforceRegion: boolean allowForwardedHeader?: boolean + allowBodyHashing?: boolean nonCanonicalForwardedHost?: string + publicUrl?: URL + s3OmitPrefixFromCanonicalUri?: string credentials: Omit & { secretKey: string } } @@ -23,11 +35,12 @@ export interface ClientSignature { interface SignatureRequest { url: string - body?: string | ReadableStream | Buffer + body?: string | ReadableStream | Buffer | Readable headers: Record method: string query?: Record prefix?: string + payloadHasher?: Writable & { digestHex: () => string } } interface Credentials { @@ -82,17 +95,26 @@ export const ALWAYS_UNSIGNABLE_QUERY_PARAMS = { 'X-Amz-Signature': true, } +export const EMPTY_SHA256_HASH = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + export class SignatureV4 { public readonly serverCredentials: SignatureV4Options['credentials'] enforceRegion: boolean allowForwardedHeader?: boolean + allowBodyHashing?: boolean nonCanonicalForwardedHost?: string + publicUrl?: URL + s3OmitPrefixFromCanonicalUri?: string + private readonly signingKeyCache = new Map() constructor(options: SignatureV4Options) { this.serverCredentials = options.credentials this.enforceRegion = options.enforceRegion this.allowForwardedHeader = options.allowForwardedHeader + this.allowBodyHashing = options.allowBodyHashing this.nonCanonicalForwardedHost = options.nonCanonicalForwardedHost + this.publicUrl = options.publicUrl + this.s3OmitPrefixFromCanonicalUri = options.s3OmitPrefixFromCanonicalUri } static parseAuthorizationHeader(headers: Record) { @@ -252,12 +274,12 @@ export class SignatureV4 { * @param clientSignature * @param request */ - verify(clientSignature: ClientSignature, request: SignatureRequest) { + async verify(clientSignature: ClientSignature, request: SignatureRequest) { if (typeof clientSignature.policy?.raw === 'string') { return this.verifyPostPolicySignature(clientSignature, clientSignature.policy.raw) } - const serverSignature = this.sign(clientSignature, request) + const serverSignature = await this.sign(clientSignature, request) return crypto.timingSafeEqual( Buffer.from(clientSignature.signature), Buffer.from(serverSignature.signature) @@ -283,11 +305,8 @@ export class SignatureV4 { chunkSignature: string, prevSignature: string = clientSignature.signature ): boolean { - const { secretKey } = this.serverCredentials const { shortDate, region, service } = clientSignature.credentials - const signingKey = this.signingKey(secretKey, shortDate, region, service) - - const emptyHash = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + const signingKey = this.getCachedSigningKey(shortDate, region, service) // Build the “String to Sign” for this chunk exactly per AWS: // AWS4-HMAC-SHA256-PAYLOAD @@ -302,7 +321,7 @@ export class SignatureV4 { clientSignature.longDate, scope, prevSignature, - emptyHash, + EMPTY_SHA256_HASH, chunkHash, ].join('\n') @@ -318,8 +337,7 @@ export class SignatureV4 { this.validateCredentials(clientSignature.credentials) const selectedRegion = this.getSelectedRegion(clientSignature.credentials.region) - const signingKey = this.signingKey( - serverCredentials.secretKey, + const signingKey = this.getCachedSigningKey( clientSignature.credentials.shortDate, selectedRegion, serverCredentials.service @@ -333,7 +351,7 @@ export class SignatureV4 { * @param clientSignature * @param request */ - sign(clientSignature: ClientSignature, request: SignatureRequest) { + async sign(clientSignature: ClientSignature, request: SignatureRequest) { const serverCredentials = this.serverCredentials this.validateCredentials(clientSignature.credentials) @@ -344,11 +362,12 @@ export class SignatureV4 { } const selectedRegion = this.getSelectedRegion(clientSignature.credentials.region) - const canonicalRequest = this.constructCanonicalRequest( + const canonicalRequest = await this.constructCanonicalRequest( clientSignature, request, clientSignature.signedHeaders ) + const stringToSign = this.constructStringToSign( longDate, clientSignature.credentials.shortDate, @@ -356,8 +375,8 @@ export class SignatureV4 { serverCredentials.service, canonicalRequest ) - const signingKey = this.signingKey( - serverCredentials.secretKey, + + const signingKey = this.getCachedSigningKey( clientSignature.credentials.shortDate, selectedRegion, serverCredentials.service @@ -366,7 +385,7 @@ export class SignatureV4 { return { signature: this.hmac(signingKey, stringToSign).toString('hex'), canonicalRequest } } - protected getPayloadHash(clientSignature: ClientSignature, request: SignatureRequest) { + protected async getPayloadHash(clientSignature: ClientSignature, request: SignatureRequest) { const body = request.body // For presigned URLs and GET requests, use UNSIGNED-PAYLOAD @@ -380,8 +399,8 @@ export class SignatureV4 { } // If the body is undefined, use the hash of an empty string - if (body == undefined) { - return 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + if (body === null || body === undefined) { + return EMPTY_SHA256_HASH } // Calculate the SHA256 hash of the body @@ -392,47 +411,57 @@ export class SignatureV4 { .digest('hex') } + // If body is a ReadableStream, calculate the SHA256 hash of the stream + if (body instanceof Readable && this.allowBodyHashing && request.payloadHasher) { + return await pipeline(body, request.payloadHasher).then(() => { + return request.payloadHasher?.digestHex() + }) + } + // Default to UNSIGNED-PAYLOAD if body is not a string or ArrayBuffer return 'UNSIGNED-PAYLOAD' } - protected constructCanonicalRequest( + protected async constructCanonicalRequest( clientSignature: ClientSignature, request: SignatureRequest, signedHeaders: string[] ) { const method = request.method - const canonicalUri = this.constructCanonicalUri(request) - + const prefix = request.prefix ? request.prefix.replace(/\/+$/, '') : '' + let canonicalUri = new URL(`http://localhost:8080${prefix}${request.url}`).pathname + if (this.s3OmitPrefixFromCanonicalUri) { + canonicalUri = canonicalUri.replace(new RegExp(`^${this.s3OmitPrefixFromCanonicalUri}`), '') + } const canonicalQueryString = this.constructCanonicalQueryString(request.query || {}) const canonicalHeaders = this.constructCanonicalHeaders(request, signedHeaders) const signedHeadersString = signedHeaders.sort().join(';') - const payloadHash = this.getPayloadHash(clientSignature, request) + const payloadHash = await this.getPayloadHash(clientSignature, request) return `${method}\n${canonicalUri}\n${canonicalQueryString}\n${canonicalHeaders}\n${signedHeadersString}\n${payloadHash}` } - protected constructCanonicalUri(request: SignatureRequest) { - const xForwardedUri = this.getHeader(request, 'x-forwarded-uri') - if (xForwardedUri) { - return xForwardedUri - } - - const uri = new URL(`http://localhost:8080${request.prefix || ''}${request.url}`).pathname - - const xRemovePrefix = this.getHeader(request, 'x-remove-prefix') - if (xRemovePrefix) { - return uri.replace(new RegExp(`^${xRemovePrefix}`), '') - } - - return uri + /** + * Encodes a URI component according to RFC 3986, as required by AWS Signature V4. + * This differs from encodeURIComponent which doesn't encode certain characters + * like parentheses that AWS requires to be percent-encoded. + */ + protected encodeRFC3986URIComponent(str: string): string { + return encodeURIComponent(str).replace(/[!'()*]/g, (c) => { + return '%' + c.charCodeAt(0).toString(16).toUpperCase() + }) } protected constructCanonicalQueryString(query: Record) { return Object.keys(query) .filter((key) => !(key in ALWAYS_UNSIGNABLE_QUERY_PARAMS)) .sort() - .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(query[key] as string)}`) + .map( + (key) => + `${this.encodeRFC3986URIComponent(key)}=${this.encodeRFC3986URIComponent( + query[key] as string + )}` + ) .join('&') } @@ -471,6 +500,12 @@ export class SignatureV4 { } protected getHostHeader(request: SignatureRequest) { + // When a public URL is configured, use its host for signature verification. + // This avoids proxy header issues (e.g., Kong overwriting X-Forwarded-Port). + if (this.publicUrl) { + return `host:${this.publicUrl.host}` + } + if (this.allowForwardedHeader) { const forwarded = this.getHeader(request, 'forwarded') if (forwarded) { @@ -490,9 +525,7 @@ export class SignatureV4 { const xForwardedHost = this.getHeader(request, 'x-forwarded-host') if (xForwardedHost) { - return `host:${xForwardedHost.toLowerCase()}` - // TODO something always sets 'x-forwarded-port' to internal port, so we can't use it - /* const port = this.getHeader(request, 'x-forwarded-port') + const port = this.getHeader(request, 'x-forwarded-port') const host = `host:${xForwardedHost.toLowerCase()}` if (port && !['443', '80'].includes(port)) { @@ -502,7 +535,7 @@ export class SignatureV4 { return 'host:' + xForwardedHost.replace(/:\d+$/, `:${port}`) } } - return host */ + return host } return `host:${this.getHeader(request, 'host')}` @@ -561,6 +594,31 @@ export class SignatureV4 { return this.hmac(kService, 'aws4_request') } + private getCachedSigningKey(dateStamp: string, regionName: string, serviceName: string) { + const cacheKey = `${dateStamp}\0${regionName}\0${serviceName}` + let signingKey = this.signingKeyCache.get(cacheKey) + + if (!signingKey) { + signingKey = this.signingKey( + this.serverCredentials.secretKey, + dateStamp, + regionName, + serviceName + ) + this.signingKeyCache.set(cacheKey, signingKey) + } + + return signingKey + } + + protected async sha256OfRequest(req: Readable) { + const hash = createHash('sha256') + for await (const chunk of req) { + hash.update(chunk) + } + return hash.digest('hex') + } + protected hmac(key: string | Buffer, data: string): Buffer { return crypto.createHmac('sha256', key).update(data).digest() } diff --git a/src/storage/protocols/tus/als-memory-kv.ts b/src/storage/protocols/tus/als-memory-kv.ts index 1a6b8bfad..ce1dfde70 100644 --- a/src/storage/protocols/tus/als-memory-kv.ts +++ b/src/storage/protocols/tus/als-memory-kv.ts @@ -1,6 +1,6 @@ -import { AsyncLocalStorage } from 'async_hooks' -import { KvStore } from '@tus/server' import { MetadataValue } from '@tus/s3-store' +import { KvStore } from '@tus/server' +import { AsyncLocalStorage } from 'async_hooks' export class AlsMemoryKV implements KvStore { static localStorage = new AsyncLocalStorage>() diff --git a/src/storage/protocols/tus/file-store.ts b/src/storage/protocols/tus/file-store.ts index bd84fb1ff..5f779610f 100644 --- a/src/storage/protocols/tus/file-store.ts +++ b/src/storage/protocols/tus/file-store.ts @@ -1,8 +1,7 @@ -import { FileStore as TusFileStore } from '@tus/file-store' +import { ensureFile } from '@internal/fs' +import { Configstore, FileStore as TusFileStore } from '@tus/file-store' import { Upload } from '@tus/server' -import fsExtra from 'fs-extra' import path from 'path' -import { Configstore } from '@tus/file-store' import { FileBackend } from '../../backend' type FileStoreOptions = { @@ -21,7 +20,7 @@ export class FileStore extends TusFileStore { async create(file: Upload): Promise { const filePath = path.join(this.options.directory, file.id) - await fsExtra.ensureFile(filePath) + await ensureFile(filePath) await this.fileAdapter.setFileMetadata(filePath, { cacheControl: file.metadata?.cacheControl || '', diff --git a/src/storage/protocols/tus/index.ts b/src/storage/protocols/tus/index.ts index 33d3014ae..91ebb36e7 100644 --- a/src/storage/protocols/tus/index.ts +++ b/src/storage/protocols/tus/index.ts @@ -1,4 +1,4 @@ +export * from './als-memory-kv' export * from './file-store' export * from './postgres-locker' export * from './upload-id' -export * from './als-memory-kv' diff --git a/src/storage/protocols/tus/postgres-locker.ts b/src/storage/protocols/tus/postgres-locker.ts index cd66bd188..bf96fa92e 100644 --- a/src/storage/protocols/tus/postgres-locker.ts +++ b/src/storage/protocols/tus/postgres-locker.ts @@ -1,15 +1,20 @@ -import { Lock, Locker, RequestRelease } from '@tus/server' -import { clearTimeout } from 'node:timers' import EventEmitter from 'node:events' -import { Database } from '../../database' +import { clearTimeout } from 'node:timers' +import { ERRORS, ErrorCode, StorageBackendError } from '@internal/errors' import { PubSubAdapter } from '@internal/pubsub' +import { Lock, Locker, RequestRelease } from '@tus/server' +import { Database } from '../../database' import { UploadId } from './upload-id' -import { ErrorCode, ERRORS, StorageBackendError } from '@internal/errors' const REQUEST_LOCK_RELEASE_MESSAGE = 'REQUEST_LOCK_RELEASE' export class LockNotifier { protected events = new EventEmitter() + + handler = ({ id }: { id: string }) => { + this.events.emit(`release:${id}`) + } + constructor(private readonly pubSub: PubSubAdapter) {} release(id: string) { @@ -24,15 +29,20 @@ export class LockNotifier { this.events.removeAllListeners(`release:${id}`) } - async subscribe() { - await this.pubSub.subscribe(REQUEST_LOCK_RELEASE_MESSAGE, ({ id }) => { - this.events.emit(`release:${id}`) - }) + async start() { + await this.pubSub.subscribe(REQUEST_LOCK_RELEASE_MESSAGE, this.handler) + } + + stop() { + return this.pubSub.unsubscribe(REQUEST_LOCK_RELEASE_MESSAGE, this.handler) } } export class PgLocker implements Locker { - constructor(private readonly db: Database, private readonly notifier: LockNotifier) {} + constructor( + private readonly db: Database, + private readonly notifier: LockNotifier + ) {} newLock(id: string): Lock { return new PgLock(id, this.db, this.notifier) @@ -52,35 +62,40 @@ export class PgLock implements Lock { async lock(stopSignal: AbortSignal, cancelReq: RequestRelease): Promise { await new Promise((resolve, reject) => { this.db - .withTransaction(async (db) => { - const abortController = new AbortController() - let onAbort: (() => void) | undefined - - try { - onAbort = () => { + .withTransaction( + async (db) => { + const abortController = new AbortController() + let onAbort: (() => void) | undefined + + try { + onAbort = () => { + abortController.abort() + } + stopSignal.addEventListener('abort', onAbort) + + const acquired = await Promise.race([ + this.waitTimeout(5000, abortController.signal), + this.acquireLock(db, this.id, abortController.signal), + ]) + + if (!acquired) { + throw ERRORS.LockTimeout() + } + + this.isLocked = true + + await new Promise((innerResolve) => { + this.tnxResolver = innerResolve + resolve() + }) + } finally { abortController.abort() } - stopSignal.addEventListener('abort', onAbort) - - const acquired = await Promise.race([ - this.waitTimeout(5000, abortController.signal), - this.acquireLock(db, this.id, abortController.signal), - ]) - - if (!acquired) { - throw ERRORS.LockTimeout() - } - - this.isLocked = true - - await new Promise((innerResolve) => { - this.tnxResolver = innerResolve - resolve() - }) - } finally { - abortController.abort() + }, + { + timeout: 5 * 60 * 1000, // 5 minutes } - }) + ) .catch(reject) }) diff --git a/src/storage/protocols/tus/s3-locker.ts b/src/storage/protocols/tus/s3-locker.ts index 251f71c3a..7e75a4901 100644 --- a/src/storage/protocols/tus/s3-locker.ts +++ b/src/storage/protocols/tus/s3-locker.ts @@ -1,13 +1,13 @@ -import { Lock, Locker, RequestRelease } from '@tus/server' import { - S3Client, - PutObjectCommand, - GetObjectCommand, DeleteObjectCommand, DeleteObjectsCommand, + GetObjectCommand, ListObjectsV2Command, + PutObjectCommand, + S3Client, } from '@aws-sdk/client-s3' import { ERRORS } from '@internal/errors' +import { Lock, Locker, RequestRelease } from '@tus/server' import { LockNotifier } from './postgres-locker' export interface S3LockerOptions { @@ -139,6 +139,10 @@ export class S3Locker implements Locker { const body = await response.Body.transformToString() const currentLock: LockMetadata = JSON.parse(body) + if (id !== currentLock.lockId) { + return false + } + // Update expiration time const updatedLock: LockMetadata = { ...currentLock, @@ -146,12 +150,13 @@ export class S3Locker implements Locker { renewedAt: Date.now(), } - // Update the lock with new expiration + // Update the lock with new expiration (if it exists) await this.s3Client.send( new PutObjectCommand({ Bucket: this.bucket, Key: lockKey, Body: JSON.stringify(updatedLock), + IfMatch: response.ETag, ContentType: 'application/json', Metadata: { lockId: id, @@ -161,7 +166,7 @@ export class S3Locker implements Locker { ) return true } catch (error: any) { - if (error.name === 'NoSuchKey') { + if (error.name === 'NoSuchKey' || error.name === 'PreconditionFailed') { return false } throw error diff --git a/src/storage/protocols/tus/upload-id.ts b/src/storage/protocols/tus/upload-id.ts index a53d6382d..85f3cae98 100644 --- a/src/storage/protocols/tus/upload-id.ts +++ b/src/storage/protocols/tus/upload-id.ts @@ -1,7 +1,7 @@ -import { getConfig } from '../../../config' import { ERRORS } from '@internal/errors' -import { mustBeValidBucketName, mustBeValidKey } from '../../limits' +import { getConfig } from '../../../config' import { FILE_VERSION_SEPARATOR, PATH_SEPARATOR, SEPARATOR } from '../../backend' +import { mustBeValidBucketName, mustBeValidKey } from '../../limits' interface ResourceIDOptions { tenant: string diff --git a/src/storage/protocols/vector/adapter/s3-vector.ts b/src/storage/protocols/vector/adapter/s3-vector.ts new file mode 100644 index 000000000..87053b1a9 --- /dev/null +++ b/src/storage/protocols/vector/adapter/s3-vector.ts @@ -0,0 +1,130 @@ +import { + AccessDeniedException, + ConflictException, + CreateIndexCommand, + CreateIndexCommandInput, + CreateIndexCommandOutput, + DeleteIndexCommand, + DeleteIndexCommandInput, + DeleteIndexCommandOutput, + DeleteVectorsCommand, + DeleteVectorsInput, + DeleteVectorsOutput, + GetVectorsCommand, + GetVectorsCommandInput, + GetVectorsCommandOutput, + ListVectorsCommand, + ListVectorsInput, + ListVectorsOutput, + NotFoundException, + PutVectorsCommand, + PutVectorsInput, + PutVectorsOutput, + QueryVectorsCommand, + QueryVectorsInput, + QueryVectorsOutput, + S3VectorsClient, +} from '@aws-sdk/client-s3vectors' +import { ERRORS } from '@internal/errors' +import { getConfig } from '../../../../config' + +export interface VectorStore { + createVectorIndex(command: CreateIndexCommandInput): Promise + deleteVectorIndex(param: DeleteIndexCommandInput): Promise + putVectors(command: PutVectorsInput): Promise + listVectors(command: ListVectorsInput): Promise + + queryVectors(queryInput: QueryVectorsInput): Promise + + deleteVectors(deleteVectorsInput: DeleteVectorsInput): Promise + + getVectors(getVectorsInput: GetVectorsCommandInput): Promise +} + +const { storageS3Region, vectorBucketRegion } = getConfig() + +export function createS3VectorClient() { + const s3VectorClient = new S3VectorsClient({ + region: vectorBucketRegion || storageS3Region, + }) + + return new S3VectorsClient(s3VectorClient) +} + +export class S3Vector implements VectorStore { + constructor(protected readonly s3VectorClient: S3VectorsClient) {} + + getVectors(getVectorsInput: GetVectorsCommandInput): Promise { + return this.handleError( + () => this.s3VectorClient.send(new GetVectorsCommand(getVectorsInput)), + { type: 'vector', name: getVectorsInput.indexName || 'unknown' } + ) + } + + deleteVectors(deleteVectorsInput: DeleteVectorsInput): Promise { + return this.handleError( + () => this.s3VectorClient.send(new DeleteVectorsCommand(deleteVectorsInput)), + { type: 'vector', name: deleteVectorsInput.indexName || 'unknown' } + ) + } + + queryVectors(queryInput: QueryVectorsInput): Promise { + return this.handleError(() => this.s3VectorClient.send(new QueryVectorsCommand(queryInput)), { + type: 'vector-index', + name: queryInput.indexName || 'unknown', + }) + } + + async listVectors(command: ListVectorsInput): Promise { + return this.handleError(() => this.s3VectorClient.send(new ListVectorsCommand(command)), { + type: 'vector-index', + name: command.indexName || 'unknown', + }) + } + + putVectors(command: PutVectorsInput): Promise { + return this.handleError(() => this.s3VectorClient.send(new PutVectorsCommand(command)), { + type: 'vector', + name: command.indexName || 'unknown', + }) + } + + deleteVectorIndex(param: DeleteIndexCommandInput): Promise { + return this.handleError(() => this.s3VectorClient.send(new DeleteIndexCommand(param)), { + type: 'vector-index', + name: param.indexName || 'unknown', + }) + } + + async createVectorIndex(command: CreateIndexCommandInput): Promise { + return this.handleError(() => this.s3VectorClient.send(new CreateIndexCommand(command)), { + type: 'vector-index', + name: command.indexName || 'unknown', + }) + } + + protected async handleError( + fn: () => Promise, + resource: { type: string; name: string } + ): Promise { + try { + return await fn() + } catch (e) { + if (e instanceof ConflictException) { + throw ERRORS.S3VectorConflictException(resource.type, resource.name) + } + + if (e instanceof AccessDeniedException) { + throw ERRORS.AccessDenied( + 'Access denied to S3 Vector service. Please check your permissions.' + ) + } + + if (e instanceof NotFoundException) { + throw ERRORS.S3VectorNotFoundException(resource.type, e.message) + } + + throw e + } + } +} diff --git a/src/storage/protocols/vector/index.ts b/src/storage/protocols/vector/index.ts new file mode 100644 index 000000000..faa7e6de0 --- /dev/null +++ b/src/storage/protocols/vector/index.ts @@ -0,0 +1,3 @@ +export * from './adapter/s3-vector' +export * from './knex' +export * from './vector-store' diff --git a/src/storage/protocols/vector/knex.ts b/src/storage/protocols/vector/knex.ts new file mode 100644 index 000000000..445a1b78b --- /dev/null +++ b/src/storage/protocols/vector/knex.ts @@ -0,0 +1,273 @@ +import { ListVectorBucketsInput } from '@aws-sdk/client-s3vectors' +import { wait } from '@internal/concurrency' +import { ERRORS } from '@internal/errors' +import { hashStringToInt } from '@internal/hashing' +import { escapeLike } from '@storage/database' +import { VectorBucket } from '@storage/schemas' +import { VectorIndex } from '@storage/schemas/vector' +import { Knex } from 'knex' +import { DatabaseError } from 'pg' + +type DBVectorIndex = VectorIndex & { id: string; created_at: Date; updated_at: Date } +export type VectorLockResourceType = 'bucket' | 'index' | 'global' + +interface CreateVectorIndexParams { + dataType: string + dimension: number + distanceMetric: string + indexName: string + metadataConfiguration?: { + nonFilterableMetadataKeys?: string[] + } + vectorBucketName: string +} + +export interface ListIndexesInput { + bucketId: string + maxResults?: number + nextToken?: string | undefined + prefix?: string | undefined +} + +export interface ListIndexesResult { + indexes: Pick[] + nextToken?: string +} + +export interface ListBucketResult { + vectorBuckets: VectorBucket[] + nextToken?: string +} + +export interface VectorMetadataDB { + withTransaction( + fn: (db: KnexVectorMetadataDB) => T, + config?: Knex.TransactionConfig + ): Promise + + lockResource(resourceType: VectorLockResourceType, resourceId: string): Promise + + findVectorBucket(vectorBucketName: string): Promise + createVectorBucket(bucketName: string): Promise + deleteVectorBucket(bucketName: string, vectorIndexName: string): Promise + listBuckets(param: ListVectorBucketsInput): Promise + countBuckets(): Promise + + countIndexes(bucketId: string): Promise + createVectorIndex(data: CreateVectorIndexParams): Promise + getIndex(bucketId: string, name: string): Promise + listIndexes(command: ListIndexesInput): Promise + deleteVectorIndex(bucketName: string, vectorIndexName: string): Promise + findVectorIndexForBucket(vectorBucketName: string, indexName: string): Promise +} + +export class KnexVectorMetadataDB implements VectorMetadataDB { + constructor(protected readonly knex: Knex) {} + + lockResource(resourceType: VectorLockResourceType, resourceId: string): Promise { + const lockId = hashStringToInt(`vector:${resourceType}:${resourceId}`) + return this.knex.raw('SELECT pg_advisory_xact_lock(?::bigint)', [lockId]) + } + + async countIndexes(bucketId: string): Promise { + const row = await this.knex + .withSchema('storage') + .table('vector_indexes') + .where({ bucket_id: bucketId }) + .count<{ count: string }>('id as count') + .first() + return Number(row?.count ?? 0) + } + + async countBuckets(): Promise { + const row = await this.knex + .withSchema('storage') + .table('buckets_vectors') + .count<{ count: string }>('id as count') + .first() + + return Number(row?.count ?? 0) + } + + async listBuckets(param: ListVectorBucketsInput): Promise { + const query = this.knex.withSchema('storage').table('buckets_vectors') + if (param.prefix) { + query.where('id', 'like', `${escapeLike(param.prefix)}%`) + } + + if (param.nextToken) { + query.andWhere('id', '>', param.nextToken) + } + const maxResults = param.maxResults ? Math.min(param.maxResults, 500) : 500 + + const result = await query.orderBy('id', 'asc').limit(maxResults + 1) + + const hasMore = result.length > maxResults + + const buckets = result.slice(0, maxResults) + + return { + vectorBuckets: buckets, + nextToken: hasMore ? buckets[buckets.length - 1].id : undefined, + } + } + + async findVectorIndexForBucket( + vectorBucketName: string, + indexName: string + ): Promise { + const index = await this.knex + .withSchema('storage') + .select('*') + .table('vector_indexes') + .where({ bucket_id: vectorBucketName, name: indexName }) + .first() + + if (!index) { + throw ERRORS.S3VectorNotFoundException('vector index', indexName) + } + return index + } + + async findVectorBucket(vectorBucketName: string): Promise { + const bucket = await this.knex + .withSchema('storage') + .table('buckets_vectors') + .where({ id: vectorBucketName }) + .first() + + if (!bucket) { + throw ERRORS.S3VectorNotFoundException('vector bucket', vectorBucketName) + } + + return bucket + } + + async createVectorBucket(bucketName: string): Promise { + try { + await this.knex.withSchema('storage').table('buckets_vectors').insert({ + id: bucketName, + }) + } catch (e) { + if (e instanceof Error && e instanceof DatabaseError) { + if (e.code === '23505') { + throw ERRORS.S3VectorConflictException('vector bucket', bucketName) + } + } + + throw e + } + } + + async listIndexes(command: ListIndexesInput): Promise { + const maxResults = command.maxResults ? Math.min(command.maxResults, 500) : 500 + + const query = this.knex + .withSchema('storage') + .select('name', 'bucket_id', 'created_at') + .from('vector_indexes') + .where({ bucket_id: command.bucketId }) + .orderBy('name', 'asc') + .table('vector_indexes') + + if (command.prefix) { + query.andWhere('name', 'like', `${escapeLike(command.prefix)}%`) + } + + if (command.nextToken) { + query.andWhere('name', '>', command.nextToken) + } + + const result = await query.limit(maxResults + 1) + const hasMore = result.length > maxResults + + const indexes = result.slice(0, maxResults) + + return { + indexes, + nextToken: hasMore ? indexes[indexes.length - 1].name : undefined, + } + } + + async getIndex(bucketId: string, name: string): Promise { + const index = await this.knex + .withSchema('storage') + .select('*') + .table('vector_indexes') + .where({ bucket_id: bucketId, name }) + .first() + + if (!index) { + throw ERRORS.S3VectorNotFoundException('vector index', name) + } + return index + } + + async createVectorIndex(data: CreateVectorIndexParams) { + try { + return await this.knex + .withSchema('storage') + .table('vector_indexes') + .insert({ + bucket_id: data.vectorBucketName, + data_type: data.dataType, + name: data.indexName, + dimension: data.dimension, + distance_metric: data.distanceMetric, + metadata_configuration: data.metadataConfiguration, + }) + } catch (e) { + if (e instanceof Error && e instanceof DatabaseError) { + if (e.code === '23505') { + throw ERRORS.S3VectorConflictException('vector index', data.indexName) + } + } + throw e + } + } + + async withTransaction( + fn: (db: KnexVectorMetadataDB) => T, + config?: Knex.TransactionConfig + ): Promise { + const maxRetries = 3 + let attempt = 0 + let lastError: Error | undefined = undefined + + while (attempt < maxRetries) { + try { + return await this.knex.transaction(async (trx) => { + const trxDb = new KnexVectorMetadataDB(trx) + const result = await fn(trxDb) + return result + }, config) + } catch (error) { + attempt++ + + // Check if it's a serialization error (PostgreSQL error code 40001) + if (error instanceof DatabaseError && error.code === '40001' && attempt < maxRetries) { + // Exponential backoff: 20ms, 40ms, 80ms + await wait(20 * Math.pow(2, attempt - 1)) + lastError = error + continue + } + + throw error + } + } + + throw ERRORS.TransactionError(`Transaction failed after maximum ${attempt} retries`, lastError) + } + + deleteVectorIndex(bucketName: string, vectorIndexName: string): Promise { + return this.knex + .withSchema('storage') + .table('vector_indexes') + .where({ bucket_id: bucketName, name: vectorIndexName }) + .del() + } + + async deleteVectorBucket(bucketName: string) { + await this.knex.withSchema('storage').table('buckets_vectors').where({ id: bucketName }).del() + } +} diff --git a/src/storage/protocols/vector/vector-store.test.ts b/src/storage/protocols/vector/vector-store.test.ts new file mode 100644 index 000000000..47947c446 --- /dev/null +++ b/src/storage/protocols/vector/vector-store.test.ts @@ -0,0 +1,368 @@ +import { + ConflictException, + CreateIndexCommandOutput, + DeleteIndexCommandOutput, + DeleteVectorsOutput, + GetVectorsCommandOutput, + ListVectorsOutput, + PutVectorsOutput, + QueryVectorsOutput, +} from '@aws-sdk/client-s3vectors' +import { ERRORS } from '@internal/errors' +import { Sharder } from '@internal/sharding' +import { VectorBucket } from '@storage/schemas' +import { type Mocked, vi } from 'vitest' +import { type VectorStore } from './adapter/s3-vector' +import { + type KnexVectorMetadataDB, + type VectorLockResourceType, + type VectorMetadataDB, +} from './knex' +import { VECTOR_BUCKET_COUNT_LOCK, VectorStoreManager } from './vector-store' + +function createMockVectorStore(): Mocked { + return { + createVectorIndex: vi.fn().mockResolvedValue({} as CreateIndexCommandOutput), + deleteVectorIndex: vi.fn().mockResolvedValue({} as DeleteIndexCommandOutput), + putVectors: vi.fn().mockResolvedValue({} as PutVectorsOutput), + listVectors: vi.fn().mockResolvedValue({} as ListVectorsOutput), + queryVectors: vi.fn().mockResolvedValue({} as QueryVectorsOutput), + deleteVectors: vi.fn().mockResolvedValue({} as DeleteVectorsOutput), + getVectors: vi.fn().mockResolvedValue({} as GetVectorsCommandOutput), + } +} + +function createMockSharder(): Mocked { + return { + createShard: vi.fn(), + setShardStatus: vi.fn(), + reserve: vi.fn(), + confirm: vi.fn(), + cancel: vi.fn(), + expireLeases: vi.fn(), + freeByLocation: vi.fn(), + freeByResource: vi.fn(), + shardStats: vi.fn(), + findShardByResourceId: vi.fn(), + listShardByKind: vi.fn(), + withTnx: vi.fn(), + } as unknown as Mocked +} + +function createMockVectorDb(): Mocked { + return { + withTransaction: vi.fn(), + lockResource: vi.fn(), + findVectorBucket: vi.fn(), + createVectorBucket: vi.fn(), + deleteVectorBucket: vi.fn(), + listBuckets: vi.fn(), + countBuckets: vi.fn(), + countIndexes: vi.fn(), + createVectorIndex: vi.fn(), + getIndex: vi.fn(), + listIndexes: vi.fn(), + deleteVectorIndex: vi.fn(), + findVectorIndexForBucket: vi.fn(), + } as unknown as Mocked +} + +function createVectorBucketRecord(bucketName: string): VectorBucket { + return { + id: bucketName, + created_at: new Date(), + updated_at: new Date().toISOString(), + } as unknown as VectorBucket +} + +function createDeterministicVectorDb(options: { + bucketCount: number + existingBuckets?: string[] + onLockResource?: ( + resourceType: VectorLockResourceType, + resourceId: string + ) => Promise | void + onCreateVectorBucket?: (bucketName: string) => Promise | void + onDeleteVectorBucket?: (bucketName: string) => Promise | void +}): VectorMetadataDB { + const state = { + bucketCount: options.bucketCount, + existingBuckets: new Set(options.existingBuckets ?? []), + countLockHeld: false, + countLockWaiters: [] as Array<() => void>, + } + + async function acquireCountLock() { + if (!state.countLockHeld) { + state.countLockHeld = true + return + } + + await new Promise((resolve) => { + state.countLockWaiters.push(resolve) + }) + } + + function releaseCountLock() { + const next = state.countLockWaiters.shift() + if (next) { + next() + return + } + + state.countLockHeld = false + } + + return { + async withTransaction(fn: (db: KnexVectorMetadataDB) => T): Promise { + let holdsCountLock = false + + const tx: Partial = { + lockResource: async (resourceType: VectorLockResourceType, resourceId: string) => { + await options.onLockResource?.(resourceType, resourceId) + + if (resourceType === 'global' && resourceId === VECTOR_BUCKET_COUNT_LOCK) { + await acquireCountLock() + holdsCountLock = true + } + }, + findVectorBucket: async (bucketName: string) => { + if (state.existingBuckets.has(bucketName)) { + return createVectorBucketRecord(bucketName) + } + + throw ERRORS.S3VectorNotFoundException('vector bucket', bucketName) + }, + countBuckets: async () => state.bucketCount, + createVectorBucket: async (bucketName: string) => { + await options.onCreateVectorBucket?.(bucketName) + + if (state.existingBuckets.has(bucketName)) { + throw new ConflictException({ + message: `vector bucket "${bucketName}" already exists`, + $metadata: {}, + }) + } + + state.existingBuckets.add(bucketName) + state.bucketCount += 1 + }, + listIndexes: async () => ({ indexes: [] }), + deleteVectorBucket: async (bucketName: string) => { + await options.onDeleteVectorBucket?.(bucketName) + + if (state.existingBuckets.delete(bucketName)) { + state.bucketCount -= 1 + } + }, + } + + try { + return await fn(tx as KnexVectorMetadataDB) + } finally { + if (holdsCountLock) { + releaseCountLock() + } + } + }, + lockResource: async () => undefined, + findVectorBucket: async () => { + throw new Error('not implemented') + }, + createVectorBucket: async () => undefined, + deleteVectorBucket: async () => undefined, + listBuckets: async () => ({ vectorBuckets: [] }), + countBuckets: async () => state.bucketCount, + countIndexes: async () => 0, + createVectorIndex: async () => { + throw new Error('not implemented') + }, + getIndex: async () => { + throw new Error('not implemented') + }, + listIndexes: async () => ({ indexes: [] }), + deleteVectorIndex: async () => undefined, + findVectorIndexForBucket: async () => { + throw new Error('not implemented') + }, + } +} + +describe('VectorStoreManager bucket lifecycle', () => { + it('serializes concurrent creates for the final bucket slot', async () => { + const releaseFirstCreate = Promise.withResolvers() + const firstCreateStarted = Promise.withResolvers() + + const db = createDeterministicVectorDb({ + bucketCount: 1, + existingBuckets: ['existing-bucket'], + onCreateVectorBucket: async (bucketName) => { + if (bucketName === 'bucket-a') { + firstCreateStarted.resolve() + await releaseFirstCreate.promise + } + }, + }) + + const manager = new VectorStoreManager(createMockVectorStore(), db, createMockSharder(), { + tenantId: 'test-tenant', + maxBucketCount: 2, + maxIndexCount: Infinity, + }) + + const createA = manager.createBucket('bucket-a') + await firstCreateStarted.promise + + const createB = manager.createBucket('bucket-b') + releaseFirstCreate.resolve() + + const results = await Promise.allSettled([createA, createB]) + + expect(results).toEqual([ + { status: 'fulfilled', value: undefined }, + { + status: 'rejected', + reason: expect.objectContaining({ code: 'S3VectorMaxBucketsExceeded' }), + }, + ]) + }) + + it('keeps createBucket idempotent for an existing bucket even when at capacity', async () => { + const db = createDeterministicVectorDb({ + bucketCount: 1, + existingBuckets: ['bucket-a'], + }) + + const manager = new VectorStoreManager(createMockVectorStore(), db, createMockSharder(), { + tenantId: 'test-tenant', + maxBucketCount: 1, + maxIndexCount: Infinity, + }) + + await expect(manager.createBucket('bucket-a')).resolves.toBeUndefined() + await expect(manager.createBucket('bucket-b')).rejects.toMatchObject({ + code: 'S3VectorMaxBucketsExceeded', + }) + }) + + it('shares the bucket-count lock between delete and create so capacity is observed after delete commits', async () => { + const releaseDelete = Promise.withResolvers() + const deleteReachedRemoval = Promise.withResolvers() + + const db = createDeterministicVectorDb({ + bucketCount: 1, + existingBuckets: ['bucket-to-delete'], + onDeleteVectorBucket: async (bucketName) => { + if (bucketName === 'bucket-to-delete') { + deleteReachedRemoval.resolve() + await releaseDelete.promise + } + }, + }) + + const manager = new VectorStoreManager(createMockVectorStore(), db, createMockSharder(), { + tenantId: 'test-tenant', + maxBucketCount: 1, + maxIndexCount: Infinity, + }) + + const deletePromise = manager.deleteBucket('bucket-to-delete') + await deleteReachedRemoval.promise + + const createPromise = manager.createBucket('bucket-new') + releaseDelete.resolve() + + await expect(deletePromise).resolves.toBeUndefined() + await expect(createPromise).resolves.toBeUndefined() + }) + + it('does not block unrelated creates while delete waits on the target bucket lock', async () => { + const releaseBucketLock = Promise.withResolvers() + const deleteWaitingOnBucketLock = Promise.withResolvers() + + const db = createDeterministicVectorDb({ + bucketCount: 1, + existingBuckets: ['bucket-a'], + onLockResource: async (resourceType, resourceId) => { + if (resourceType === 'bucket' && resourceId === 'bucket-a') { + deleteWaitingOnBucketLock.resolve() + await releaseBucketLock.promise + } + }, + }) + + const manager = new VectorStoreManager(createMockVectorStore(), db, createMockSharder(), { + tenantId: 'test-tenant', + maxBucketCount: 2, + maxIndexCount: Infinity, + }) + + const deletePromise = manager.deleteBucket('bucket-a') + await deleteWaitingOnBucketLock.promise + + await expect(manager.createBucket('bucket-b')).resolves.toBeUndefined() + + releaseBucketLock.resolve() + await expect(deletePromise).resolves.toBeUndefined() + }) + + it('takes the per-bucket lock before the global count lock during bucket deletion', async () => { + const callOrder: string[] = [] + const db = createDeterministicVectorDb({ + bucketCount: 1, + existingBuckets: ['bucket-a'], + onLockResource: (resourceType, resourceId) => { + callOrder.push(`${resourceType}:${resourceId}`) + }, + onDeleteVectorBucket: () => { + callOrder.push('delete') + }, + }) + + const manager = new VectorStoreManager(createMockVectorStore(), db, createMockSharder(), { + tenantId: 'test-tenant', + maxBucketCount: Infinity, + maxIndexCount: Infinity, + }) + + await manager.deleteBucket('bucket-a') + + expect(callOrder).toEqual(['bucket:bucket-a', `global:${VECTOR_BUCKET_COUNT_LOCK}`, 'delete']) + }) + + it('re-checks bucket existence after taking the bucket lock before creating an index', async () => { + const db = createMockVectorDb() + const sharder = createMockSharder() + const vectorStore = createMockVectorStore() + + db.findVectorBucket + .mockResolvedValueOnce(createVectorBucketRecord('bucket-a')) + .mockRejectedValueOnce(ERRORS.S3VectorNotFoundException('vector bucket', 'bucket-a')) + db.withTransaction.mockImplementation(async (fn: (db: KnexVectorMetadataDB) => unknown) => + fn(db as unknown as KnexVectorMetadataDB) + ) + + const manager = new VectorStoreManager(vectorStore, db, sharder, { + tenantId: 'test-tenant', + maxBucketCount: Infinity, + maxIndexCount: Infinity, + }) + + await expect( + manager.createVectorIndex({ + dataType: 'float32', + dimension: 4, + distanceMetric: 'cosine', + indexName: 'index-a', + vectorBucketName: 'bucket-a', + }) + ).rejects.toMatchObject({ code: 'NotFoundException' }) + + expect(db.lockResource).toHaveBeenCalledWith('bucket', 'bucket-a') + expect(db.findVectorBucket).toHaveBeenCalledTimes(2) + expect(db.countIndexes).not.toHaveBeenCalled() + expect(db.createVectorIndex).not.toHaveBeenCalled() + expect(sharder.reserve).not.toHaveBeenCalled() + expect(vectorStore.createVectorIndex).not.toHaveBeenCalled() + }) +}) diff --git a/src/storage/protocols/vector/vector-store.ts b/src/storage/protocols/vector/vector-store.ts new file mode 100644 index 000000000..60161cfe6 --- /dev/null +++ b/src/storage/protocols/vector/vector-store.ts @@ -0,0 +1,482 @@ +import { + ConflictException, + CreateIndexInput, + DeleteIndexInput, + DeleteVectorsInput, + DistanceMetric, + GetIndexCommandInput, + GetIndexOutput, + GetVectorBucketInput, + GetVectorsCommandInput, + ListIndexesInput, + ListVectorBucketsInput, + ListVectorsInput, + MetadataConfiguration, + PutVectorsInput, + QueryVectorsInput, +} from '@aws-sdk/client-s3vectors' +import { ERRORS } from '@internal/errors' +import { ErrorCode } from '@internal/errors/codes' +import { logger, logSchema } from '@internal/monitoring' +import { Sharder } from '@internal/sharding/sharder' +import { VectorStore } from './adapter/s3-vector' +import { VectorMetadataDB } from './knex' + +interface VectorStoreConfig { + tenantId: string + maxBucketCount: number + maxIndexCount: number +} + +export const VECTOR_BUCKET_COUNT_LOCK = '__vector_bucket_count__' + +export class VectorStoreManager { + constructor( + protected readonly vectorStore: VectorStore, + protected readonly db: VectorMetadataDB, + protected readonly sharding: Sharder, + protected readonly config: VectorStoreConfig + ) {} + + protected getIndexName(name: string) { + return `${this.config.tenantId}-${name}` + } + + async createBucket(bucketName: string): Promise { + await this.db.withTransaction(async (tnx) => { + await tnx.lockResource('global', VECTOR_BUCKET_COUNT_LOCK) + + const bucketCount = await tnx.countBuckets() + if (bucketCount >= this.config.maxBucketCount) { + try { + await tnx.findVectorBucket(bucketName) + return + } catch (e) { + if ((e as { code?: string }).code !== ErrorCode.S3VectorNotFoundException) { + throw e + } + } + + throw ERRORS.S3VectorMaxBucketsExceeded(this.config.maxBucketCount) + } + + try { + await tnx.createVectorBucket(bucketName) + } catch (e) { + if (e instanceof ConflictException) { + return + } + throw e + } + }) + } + + async deleteBucket(bucketName: string): Promise { + await this.db.withTransaction(async (tx) => { + await tx.lockResource('bucket', bucketName) + await tx.lockResource('global', VECTOR_BUCKET_COUNT_LOCK) + + const indexes = await tx.listIndexes({ bucketId: bucketName, maxResults: 1 }) + + if (indexes.indexes.length > 0) { + throw ERRORS.S3VectorBucketNotEmpty(bucketName) + } + + await tx.deleteVectorBucket(bucketName) + }) + } + + async getBucket(command: GetVectorBucketInput) { + if (!command.vectorBucketName) { + throw ERRORS.MissingParameter('vectorBucketName') + } + + const vectorBucket = await this.db.findVectorBucket(command.vectorBucketName) + + return { + vectorBucket: { + vectorBucketName: vectorBucket.id, + creationTime: vectorBucket.created_at + ? Math.floor(vectorBucket.created_at.getTime() / 1000) + : undefined, + }, + } + } + + async listBuckets(command: ListVectorBucketsInput) { + const bucketResult = await this.db.listBuckets({ + maxResults: command.maxResults, + nextToken: command.nextToken, + prefix: command.prefix, + }) + + return { + vectorBuckets: bucketResult.vectorBuckets.map((bucket) => ({ + vectorBucketName: bucket.id, + creationTime: bucket.created_at + ? Math.floor(bucket.created_at.getTime() / 1000) + : undefined, + })), + nextToken: bucketResult.nextToken, + } + } + + // Store it in MultiTenantDB + // Queue Job in the same transaction + // Poll for job completion + async createVectorIndex(command: CreateIndexInput): Promise { + if (!command.indexName) { + throw ERRORS.MissingParameter('indexName') + } + + if (!command.vectorBucketName) { + throw ERRORS.MissingParameter('vectorBucketName') + } + + await this.db.findVectorBucket(command.vectorBucketName) + + const createIndexInput = { + ...command, + indexName: this.getIndexName(command.indexName), + } + + let shardReservation: { reservationId: string; shardKey: string; shardId: string } | undefined + + try { + await this.db.withTransaction(async (tx) => { + await tx.lockResource('bucket', command.vectorBucketName!) + await tx.findVectorBucket(command.vectorBucketName!) + + const indexCount = await tx.countIndexes(command.vectorBucketName!) + + if (indexCount >= this.config.maxIndexCount) { + throw ERRORS.S3VectorMaxIndexesExceeded(this.config.maxIndexCount) + } + + await tx.createVectorIndex({ + dataType: createIndexInput.dataType!, + dimension: createIndexInput.dimension!, + distanceMetric: createIndexInput.distanceMetric!, + indexName: command.indexName!, + metadataConfiguration: createIndexInput.metadataConfiguration, + vectorBucketName: command.vectorBucketName!, + }) + + shardReservation = await this.sharding.reserve({ + kind: 'vector', + bucketName: command.vectorBucketName!, + tenantId: this.config.tenantId, + logicalName: command.indexName!, + }) + + if (!shardReservation) { + throw ERRORS.NoAvailableShard() + } + + try { + if ( + createIndexInput.metadataConfiguration && + createIndexInput.metadataConfiguration.nonFilterableMetadataKeys && + createIndexInput.metadataConfiguration.nonFilterableMetadataKeys.length === 0 + ) { + delete createIndexInput.metadataConfiguration + } + + await this.vectorStore.createVectorIndex({ + ...createIndexInput, + vectorBucketName: shardReservation.shardKey, + }) + + await this.sharding.confirm(shardReservation.reservationId, { + kind: 'vector', + bucketName: command.vectorBucketName!, + tenantId: this.config.tenantId, + logicalName: command.indexName!, + }) + } catch (e) { + logSchema.error(logger, 'Vector index creation failed', { + type: 'vector', + error: e, + project: this.config.tenantId, + }) + if (e instanceof ConflictException) { + await this.sharding.confirm(shardReservation.reservationId, { + kind: 'vector', + bucketName: command.vectorBucketName!, + tenantId: this.config.tenantId, + logicalName: command.indexName!, + }) + return + } + + throw e + } + }) + } catch (error) { + logSchema.error(logger, 'Create vector index transaction failed', { + type: 'vector', + error, + project: this.config.tenantId, + }) + if (shardReservation) { + await this.sharding.cancel(shardReservation.reservationId) + } + throw error + } + } + + async deleteIndex(command: DeleteIndexInput): Promise { + if (!command.indexName) { + throw ERRORS.MissingParameter('indexName') + } + + if (!command.vectorBucketName) { + throw ERRORS.MissingParameter('vectorBucketName') + } + + await this.db.findVectorIndexForBucket(command.vectorBucketName, command.indexName) + + const vectorIndexName = this.getIndexName(command.indexName) + + await this.db.withTransaction(async (tx) => { + const shard = await this.sharding.findShardByResourceId({ + kind: 'vector', + tenantId: this.config.tenantId, + logicalName: command.indexName!, + bucketName: command.vectorBucketName!, + }) + + if (!shard) { + throw ERRORS.NoAvailableShard() + } + + await tx.deleteVectorIndex(command.vectorBucketName!, command.indexName!) + + await this.sharding.freeByResource(shard.id, { + kind: 'vector', + tenantId: this.config.tenantId, + bucketName: command.vectorBucketName!, + logicalName: command.indexName!, + }) + + await this.vectorStore.deleteVectorIndex({ + vectorBucketName: shard.shard_key, + indexName: vectorIndexName, + }) + }) + } + + async getIndex(command: GetIndexCommandInput): Promise { + if (!command.indexName) { + throw ERRORS.MissingParameter('indexName') + } + + if (!command.vectorBucketName) { + throw ERRORS.MissingParameter('vectorBucketName') + } + + const index = await this.db.getIndex(command.vectorBucketName, command.indexName) + + return { + index: { + indexName: index.name, + dataType: index.data_type as 'float32', + dimension: index.dimension, + distanceMetric: index.distance_metric as DistanceMetric, + metadataConfiguration: index.metadata_configuration as MetadataConfiguration, + vectorBucketName: index.bucket_id, + creationTime: index.created_at, + indexArn: undefined, + }, + } + } + + async listIndexes(command: ListIndexesInput) { + if (!command.vectorBucketName) { + throw ERRORS.MissingParameter('vectorBucketName') + } + + const result = await this.db.listIndexes({ + bucketId: command.vectorBucketName, + maxResults: command.maxResults, + nextToken: command.nextToken, + prefix: command.prefix, + }) + + return { + indexes: result.indexes.map((i) => ({ + indexName: i.name, + vectorBucketName: i.bucket_id, + creationTime: Math.floor(i.created_at.getTime() / 1000), + })), + nextToken: result.nextToken, + } + } + + async putVectors(command: PutVectorsInput) { + if (!command.indexName) { + throw ERRORS.MissingParameter('indexName') + } + + if (!command.vectorBucketName) { + throw ERRORS.MissingParameter('vectorBucketName') + } + + const [shard] = await Promise.all([ + this.sharding.findShardByResourceId({ + kind: 'vector', + tenantId: this.config.tenantId, + logicalName: command.indexName!, + bucketName: command.vectorBucketName!, + }), + this.db.findVectorIndexForBucket(command.vectorBucketName, command.indexName), + ]) + + if (!shard) { + throw ERRORS.NoAvailableShard() + } + + const putVectorsInput = { + ...command, + vectorBucketName: shard.shard_key, + indexName: this.getIndexName(command.indexName), + } + await this.vectorStore.putVectors(putVectorsInput) + } + + async deleteVectors(command: DeleteVectorsInput) { + if (!command.indexName) { + throw ERRORS.MissingParameter('indexName') + } + + if (!command.vectorBucketName) { + throw ERRORS.MissingParameter('vectorBucketName') + } + + const [shard] = await Promise.all([ + this.sharding.findShardByResourceId({ + kind: 'vector', + tenantId: this.config.tenantId, + logicalName: command.indexName!, + bucketName: command.vectorBucketName!, + }), + this.db.findVectorIndexForBucket(command.vectorBucketName, command.indexName), + ]) + + if (!shard) { + throw ERRORS.NoAvailableShard() + } + + const deleteVectorsInput = { + ...command, + vectorBucketName: shard.shard_key, + indexName: this.getIndexName(command.indexName), + } + + return this.vectorStore.deleteVectors(deleteVectorsInput) + } + + async listVectors(command: ListVectorsInput) { + if (!command.indexName) { + throw ERRORS.MissingParameter('indexName') + } + + if (!command.vectorBucketName) { + throw ERRORS.MissingParameter('vectorBucketName') + } + + const [shard] = await Promise.all([ + this.sharding.findShardByResourceId({ + kind: 'vector', + tenantId: this.config.tenantId, + logicalName: command.indexName!, + bucketName: command.vectorBucketName!, + }), + this.db.findVectorIndexForBucket(command.vectorBucketName, command.indexName), + ]) + + if (!shard) { + throw ERRORS.NoAvailableShard() + } + + const listVectorsInput = { + ...command, + vectorBucketName: shard.shard_key, + indexName: this.getIndexName(command.indexName), + } + + const result = await this.vectorStore.listVectors(listVectorsInput) + + return { + vectors: result.vectors, + nextToken: result.nextToken, + } + } + + async queryVectors(command: QueryVectorsInput) { + if (!command.indexName) { + throw ERRORS.MissingParameter('indexName') + } + + if (!command.vectorBucketName) { + throw ERRORS.MissingParameter('vectorBucketName') + } + + const [shard] = await Promise.all([ + this.sharding.findShardByResourceId({ + kind: 'vector', + tenantId: this.config.tenantId, + logicalName: command.indexName!, + bucketName: command.vectorBucketName!, + }), + this.db.findVectorIndexForBucket(command.vectorBucketName, command.indexName), + ]) + + if (!shard) { + throw ERRORS.NoAvailableShard() + } + + const queryInput = { + ...command, + vectorBucketName: shard.shard_key, + indexName: this.getIndexName(command.indexName), + } + return this.vectorStore.queryVectors(queryInput) + } + + async getVectors(command: GetVectorsCommandInput) { + if (!command.indexName) { + throw ERRORS.MissingParameter('indexName') + } + + if (!command.vectorBucketName) { + throw ERRORS.MissingParameter('vectorBucketName') + } + + const [shard] = await Promise.all([ + this.sharding.findShardByResourceId({ + kind: 'vector', + tenantId: this.config.tenantId, + logicalName: command.indexName!, + bucketName: command.vectorBucketName!, + }), + this.db.findVectorIndexForBucket(command.vectorBucketName, command.indexName), + ]) + + if (!shard) { + throw ERRORS.NoAvailableShard() + } + + const getVectorsInput = { + ...command, + vectorBucketName: shard.shard_key, + indexName: this.getIndexName(command.indexName), + } + + const result = await this.vectorStore.getVectors(getVectorsInput) + + return { + vectors: result.vectors, + } + } +} diff --git a/src/storage/renderer/head.ts b/src/storage/renderer/head.ts index 973fd1e11..8978d37e8 100644 --- a/src/storage/renderer/head.ts +++ b/src/storage/renderer/head.ts @@ -1,8 +1,8 @@ -import { AssetResponse, Renderer, RenderOptions } from './renderer' +import { ERRORS } from '@internal/errors' import { FastifyReply, FastifyRequest } from 'fastify' import { ObjectMetadata } from '../backend' import { ImageRenderer, TransformOptions } from './image' -import { ERRORS } from '@internal/errors' +import { AssetResponse, Renderer, RenderOptions } from './renderer' /** * HeadRenderer diff --git a/src/storage/renderer/image.ts b/src/storage/renderer/image.ts index 1e5c924a4..75263a66d 100644 --- a/src/storage/renderer/image.ts +++ b/src/storage/renderer/image.ts @@ -1,12 +1,12 @@ -import { ObjectMetadata, StorageBackendAdapter } from '../backend' +import { ERRORS } from '@internal/errors' +import Agent from 'agentkeepalive' import axios, { Axios, AxiosError } from 'axios' -import { getConfig } from '../../config' -import { FastifyRequest } from 'fastify' -import { Renderer, RenderOptions } from './renderer' import axiosRetry from 'axios-retry' -import { ERRORS } from '@internal/errors' +import { FastifyRequest } from 'fastify' import { Stream } from 'stream' -import Agent from 'agentkeepalive' +import { getConfig } from '../../config' +import { ObjectMetadata, StorageBackendAdapter } from '../backend' +import { Renderer, RenderOptions } from './renderer' /** * All the transformations options available @@ -15,7 +15,7 @@ export interface TransformOptions { width?: number height?: number resize?: 'cover' | 'contain' | 'fill' - format?: 'origin' | 'avif' + format?: 'origin' | 'avif' | 'webp' quality?: number } @@ -244,8 +244,8 @@ export class ImageRenderer extends Renderer { metadata: { httpStatusCode: response.status, size: contentLength, - contentLength: contentLength, - lastModified: lastModified, + contentLength, + lastModified, eTag: headObj.eTag, cacheControl: headObj.cacheControl, mimetype: response.headers['content-type'], diff --git a/src/storage/renderer/index.ts b/src/storage/renderer/index.ts index a33d52a4d..ae66b7079 100644 --- a/src/storage/renderer/index.ts +++ b/src/storage/renderer/index.ts @@ -1,4 +1,4 @@ -export * from './head' export * from './asset' +export * from './head' export * from './image' export * from './renderer' diff --git a/src/storage/renderer/info.ts b/src/storage/renderer/info.ts index 2a069ab30..9d6daa161 100644 --- a/src/storage/renderer/info.ts +++ b/src/storage/renderer/info.ts @@ -1,8 +1,8 @@ +import { ImageRenderer, TransformOptions } from '@storage/renderer/image' import { Obj } from '@storage/schemas' +import { FastifyReply, FastifyRequest } from 'fastify' import { HeadRenderer } from './head' -import { FastifyRequest, FastifyReply } from 'fastify' import { AssetResponse, RenderOptions } from './renderer' -import { ImageRenderer, TransformOptions } from '@storage/renderer/image' /** * HeadRenderer diff --git a/src/storage/renderer/renderer.ts b/src/storage/renderer/renderer.ts index bb6d0f38e..d79ea2ac3 100644 --- a/src/storage/renderer/renderer.ts +++ b/src/storage/renderer/renderer.ts @@ -1,7 +1,8 @@ +import { validateXRobotsTag } from '@storage/validators/x-robots-tag' import { FastifyReply, FastifyRequest } from 'fastify' -import { ObjectMetadata } from '../backend' import { Readable } from 'stream' import { getConfig } from '../../config' +import { ObjectMetadata } from '../backend' import { Obj } from '../schemas' export interface RenderOptions { @@ -10,6 +11,7 @@ export interface RenderOptions { version: string | undefined download?: string expires?: string + xRobotsTag?: string object?: Obj signal?: AbortSignal } @@ -73,6 +75,15 @@ export abstract class Renderer { data: AssetResponse, options: RenderOptions ) { + let xRobotsTag = 'none' + if (options.xRobotsTag) { + try { + // allow overriding x-robots-tag header only with valid values + validateXRobotsTag(options.xRobotsTag) + xRobotsTag = options.xRobotsTag + } catch {} + } + response .status(data.metadata.httpStatusCode ?? 200) .header('Accept-Ranges', 'bytes') @@ -80,6 +91,7 @@ export abstract class Renderer { .header('ETag', data.metadata.eTag) .header('Content-Length', data.metadata.contentLength) .header('Last-Modified', data.metadata.lastModified?.toUTCString()) + .header('X-Robots-Tag', xRobotsTag) if (options.expires) { response.header('Expires', options.expires) diff --git a/src/storage/scanner/scanner-pagination.test.ts b/src/storage/scanner/scanner-pagination.test.ts new file mode 100644 index 000000000..dc32a0fa6 --- /dev/null +++ b/src/storage/scanner/scanner-pagination.test.ts @@ -0,0 +1,83 @@ +import { type Mock, vi } from 'vitest' + +vi.mock('@storage/events/objects/backup-object', () => ({ + BackupObjectEvent: { + batchSend: vi.fn(), + }, +})) + +import { ObjectScanner } from '@storage/scanner/scanner' +import type { Storage } from '@storage/storage' + +class TestObjectScanner extends ObjectScanner { + async collectAllS3Objects(prefix: string, before?: Date) { + const pages = [] + + for await (const page of this.listAllS3Objects(prefix, { + before, + signal: new AbortController().signal, + })) { + pages.push(...page) + } + + return pages + } +} + +function makeScanner(params: { listS3Objects?: Mock }) { + const storage = { + backend: { + list: params.listS3Objects ?? vi.fn(), + }, + db: {}, + } as unknown as Storage + + return new TestObjectScanner(storage) +} + +describe('ObjectScanner pagination regressions', () => { + test('continues scanning S3 pages after an empty filtered page when a continuation token remains', async () => { + const listS3Objects = vi + .fn() + .mockResolvedValueOnce({ + keys: [ + { name: 'old-orphan-a/v0', size: 30 }, + { name: 'old-orphan-a/v0.info', size: 1 }, + ], + nextToken: 'page-2', + }) + .mockResolvedValueOnce({ + keys: [], + nextToken: 'page-3', + }) + .mockResolvedValueOnce({ + keys: [ + { name: 'old-orphan-b/v1', size: 10 }, + { name: 'old-orphan-b/v1.info', size: 1 }, + { name: 'old-orphan-c/v2', size: 20 }, + ], + nextToken: undefined, + }) + + const scanner = makeScanner({ listS3Objects }) + + await expect(scanner.collectAllS3Objects('tenant/bucket')).resolves.toEqual([ + { name: 'old-orphan-a/v0', size: 30 }, + { name: 'old-orphan-b/v1', size: 10 }, + { name: 'old-orphan-c/v2', size: 20 }, + ]) + + expect(listS3Objects).toHaveBeenCalledTimes(3) + expect(listS3Objects.mock.calls[0][1]).toMatchObject({ + prefix: 'tenant/bucket/', + }) + expect(listS3Objects.mock.calls[1][1]).toMatchObject({ + prefix: 'tenant/bucket/', + nextToken: 'page-2', + }) + expect(listS3Objects.mock.calls[2][1]).toMatchObject({ + prefix: 'tenant/bucket/', + nextToken: 'page-3', + }) + }) +}) diff --git a/src/storage/scanner/scanner.ts b/src/storage/scanner/scanner.ts index 3fb7de997..4afe50794 100644 --- a/src/storage/scanner/scanner.ts +++ b/src/storage/scanner/scanner.ts @@ -1,9 +1,9 @@ -import { Storage } from '@storage/storage' -import { getConfig } from '../../config' -import { ERRORS } from '@internal/errors' import { mergeAsyncGenerators } from '@internal/concurrency' -import { BackupObjectEvent } from '@storage/events/objects/backup-object' +import { ERRORS } from '@internal/errors' import { withOptionalVersion } from '@storage/backend' +import { BackupObjectEvent } from '@storage/events/objects/backup-object' +import { Storage } from '@storage/storage' +import { getConfig } from '../../config' const { storageS3Bucket } = getConfig() @@ -37,7 +37,6 @@ export class ObjectScanner { const localDBKeys = this.syncS3KeysToDB(tmpTable, prefix, options) try { - // eslint-disable-next-line @typescript-eslint/no-unused-vars for await (const _ of localDBKeys) { // await all of the operation finished if (options.signal.aborted) { @@ -46,8 +45,8 @@ export class ObjectScanner { } const s3Keys = this.listS3Orphans(tmpTable, { - bucket: bucket, - prefix: `${this.storage.db.tenantId}/${bucket}`, + bucket, + prefix, signal: options.signal, }) @@ -103,7 +102,6 @@ export class ObjectScanner { if (!options.tmpTable) { const s3LocalCache = this.syncS3KeysToDB(tmpTable, prefix, options) - // eslint-disable-next-line @typescript-eslint/no-unused-vars for await (const _ of s3LocalCache) { // await all of the operation finished if (options.signal.aborted) { @@ -156,11 +154,7 @@ export class ObjectScanner { ) { let nextToken: string | undefined = undefined - while (true) { - if (options.signal.aborted) { - break - } - + for (; !options.signal.aborted; ) { const storageObjects = await this.storage.db.listObjects( bucket, 'id,name,version,metadata', @@ -171,7 +165,7 @@ export class ObjectScanner { const dbKeys = storageObjects.map(({ name, version, metadata }) => { if (version) { - return { name: `${name}`, version: version, size: (metadata?.size as number) || 0 } + return { name: `${name}`, version, size: (metadata?.size as number) || 0 } } return { name, size: (metadata?.size as number) || 0 } }) @@ -197,10 +191,7 @@ export class ObjectScanner { } protected async *listAllCacheS3Keys(tableName: string, nextItem: string, signal: AbortSignal) { - while (true) { - if (signal.aborted) { - break - } + for (; !signal.aborted; ) { const query = this.storage.db.connection.pool .acquire() .table(tableName) @@ -301,27 +292,23 @@ export class ObjectScanner { ) { let nextToken: string | undefined = undefined - while (true) { - if (options.signal.aborted) { - break - } - + for (; !options.signal.aborted; ) { const result = await this.storage.backend.list(storageS3Bucket, { - prefix, + prefix: prefix + '/', nextToken, beforeDate: options.before, }) - if (result.keys.length === 0) { - break - } - nextToken = result.nextToken - yield result.keys.filter((k) => { + const keys = result.keys.filter((k) => { return k.name && !k.name.endsWith('.info') }) + if (keys.length > 0) { + yield keys + } + if (!nextToken) { break } diff --git a/src/storage/schemas/bucket.ts b/src/storage/schemas/bucket.ts index 08d16cf70..7f73ab5e6 100644 --- a/src/storage/schemas/bucket.ts +++ b/src/storage/schemas/bucket.ts @@ -32,4 +32,7 @@ export const bucketSchema = { } as const export type Bucket = FromSchema -export type IcebergCatalog = Pick +export type IcebergCatalog = Pick & { + deleted_at: Date | null +} +export type VectorBucket = Pick & { created_at: Date } diff --git a/src/storage/schemas/index.ts b/src/storage/schemas/index.ts index a9c814cfa..2cb853a56 100644 --- a/src/storage/schemas/index.ts +++ b/src/storage/schemas/index.ts @@ -1,3 +1,3 @@ -export * from './object' export * from './bucket' export * from './multipart' +export * from './object' diff --git a/src/storage/schemas/multipart.ts b/src/storage/schemas/multipart.ts index 1ca6eb296..3a1849ac8 100644 --- a/src/storage/schemas/multipart.ts +++ b/src/storage/schemas/multipart.ts @@ -15,6 +15,9 @@ export const multipartUploadSchema = { user_metadata: { anyOf: [{ type: 'object', additionalProperties: true }, { type: 'null' }], }, + metadata: { + anyOf: [{ type: 'object', additionalProperties: true }, { type: 'null' }], + }, }, required: [ 'id', diff --git a/src/storage/schemas/object.ts b/src/storage/schemas/object.ts index c7805aaba..0dbb6a0cc 100644 --- a/src/storage/schemas/object.ts +++ b/src/storage/schemas/object.ts @@ -1,5 +1,5 @@ -import { bucketSchema } from './bucket' import { FromSchema } from 'json-schema-to-ts' +import { bucketSchema } from './bucket' export const objectSchema = { $id: 'objectSchema', diff --git a/src/storage/schemas/upload.ts b/src/storage/schemas/upload.ts index 76fba5403..cffd1fc92 100644 --- a/src/storage/schemas/upload.ts +++ b/src/storage/schemas/upload.ts @@ -1,5 +1,5 @@ -import { bucketSchema } from './bucket' import { FromSchema } from 'json-schema-to-ts' +import { bucketSchema } from './bucket' export const uploadSchema = { $id: 'uploadSchema', diff --git a/src/storage/schemas/vector.ts b/src/storage/schemas/vector.ts new file mode 100644 index 000000000..aaec24de3 --- /dev/null +++ b/src/storage/schemas/vector.ts @@ -0,0 +1,26 @@ +import { FromSchema } from 'json-schema-to-ts' + +const vectorIndex = { + type: 'object', + properties: { + name: { type: 'string' }, + data_type: { type: 'string' }, + dimension: { type: 'number' }, + distance_metric: { type: 'string' }, + status: { type: 'string' }, + metadata_configuration: { + type: 'object', + properties: { + nonFilterableMetadataKeys: { + type: 'array', + items: { type: 'string' }, + }, + }, + }, + bucket_id: { type: 'string' }, + }, + required: ['name', 'dimension', 'distance_metric', 'bucket_id'], + additionalProperties: false, +} as const + +export type VectorIndex = FromSchema diff --git a/src/storage/storage.ts b/src/storage/storage.ts index 15aa5c4ec..d427393b0 100644 --- a/src/storage/storage.ts +++ b/src/storage/storage.ts @@ -1,7 +1,14 @@ +import { tenantHasFeature } from '@internal/database' +import { tenantHasMigrations } from '@internal/database/migrations' +import { ERRORS, StorageBackendError } from '@internal/errors' +import { logger, logSchema } from '@internal/monitoring' +import { BucketCreatedEvent, BucketDeleted } from '@storage/events' +import { StorageObjectLocator } from '@storage/locator' +import { InfoRenderer } from '@storage/renderer/info' +import { getConfig } from '../config' import { StorageBackendAdapter } from './backend' import { Database, FindBucketFilters, ListBucketOptions } from './database' -import { ERRORS } from '@internal/errors' -import { AssetRenderer, HeadRenderer, ImageRenderer } from './renderer' +import { ObjectAdminDeleteAllBefore } from './events' import { BucketType, getFileSizeLimit, @@ -9,15 +16,8 @@ import { mustBeValidBucketName, parseFileSizeToBytes, } from './limits' -import { getConfig } from '../config' import { ObjectStorage } from './object' -import { InfoRenderer } from '@storage/renderer/info' -import { logger, logSchema } from '@internal/monitoring' -import { StorageObjectLocator } from '@storage/locator' -import { BucketCreatedEvent, BucketDeleted } from '@storage/events' -import { tenantHasMigrations } from '@internal/database/migrations' -import { tenantHasFeature } from '@internal/database' -import { ObjectAdminDeleteAllBefore } from './events' +import { AssetRenderer, HeadRenderer, ImageRenderer } from './renderer' const { emptyBucketMax } = getConfig() @@ -56,18 +56,14 @@ export class Storage { * @param type */ renderer(type: 'asset' | 'head' | 'image' | 'info') { - switch (type) { - case 'asset': - return new AssetRenderer(this.backend) - case 'head': - return new HeadRenderer() - case 'image': - return new ImageRenderer(this.backend) - case 'info': - return new InfoRenderer() + const renderers = { + asset: () => new AssetRenderer(this.backend), + head: () => new HeadRenderer(), + image: () => new ImageRenderer(this.backend), + info: () => new InfoRenderer(), } - throw new Error(`renderer of type "${type}" not supported`) + return renderers[type]() } /** @@ -89,6 +85,10 @@ export class Storage { return this.db.listBuckets(columns, options) } + listAnalyticsBuckets(columns = 'name', options?: ListBucketOptions) { + return this.db.listAnalyticsBuckets(columns, options) + } + /** * Creates a bucket * @param data @@ -122,7 +122,7 @@ export class Storage { ) } - const icebergBucketData = data as Parameters[0] + const icebergBucketData = data as Parameters[0] return this.createIcebergBucket(icebergBucketData) } @@ -144,18 +144,35 @@ export class Storage { return this.db.createBucket(bucketData) } - async createIcebergBucket(data: Parameters[0]) { + async createIcebergBucket(data: Parameters[0]) { return this.db.withTransaction(async (db) => { - const result = await db.createIcebergBucket(data) - - await BucketCreatedEvent.invoke({ - bucketId: result.id, - type: 'ANALYTICS', - tenant: { - ref: db.tenantId, - host: db.tenantHost, + const result = await db.createAnalyticsBucket(data) + + await BucketCreatedEvent.invokeOrSend( + { + bucketId: result.id, + bucketName: result.name, + type: 'ANALYTICS', + tenant: { + ref: db.tenantId, + host: db.tenantHost, + }, }, - }) + { + sendWhenError: (error) => { + if (error instanceof StorageBackendError) { + return false + } + + logSchema.error(logger, 'Failed to invoke BucketCreatedEvent handler', { + project: db.tenantId, + type: 'event', + error, + }) + return true + }, + } + ) return result }) @@ -177,6 +194,9 @@ export class Storage { } ) { mustBeValidBucketName(id) + if (!Object.values(data).some((v) => typeof v !== 'undefined')) { + throw ERRORS.NoContentProvided() + } const bucketData: Parameters[1] = data @@ -199,13 +219,8 @@ export class Storage { /** * Delete a specific bucket if empty * @param id - * @param type */ - async deleteBucket(id: string, type: BucketType = 'STANDARD') { - if (type === 'ANALYTICS') { - return this.deleteIcebergBucket(id) - } - + async deleteBucket(id: string) { return this.db.withTransaction(async (db) => { await db.asSuperUser().findBucketById(id, 'id', { forUpdate: true, @@ -227,7 +242,7 @@ export class Storage { }) } - async deleteIcebergBucket(id: string) { + async deleteIcebergBucket(name: string) { if ( !(await tenantHasMigrations(this.db.tenantId, 'iceberg-catalog-flag-on-buckets')) || !(await tenantHasFeature(this.db.tenantId, 'icebergCatalog')) @@ -238,18 +253,15 @@ export class Storage { ) } - return this.db.withTransaction(async (db) => { - const deleted = await db.deleteAnalyticsBucket(id) - - await BucketDeleted.invoke({ - bucketId: id, - type: 'ANALYTICS', - tenant: { - ref: db.tenantId, - host: db.tenantHost, - }, - }) - return deleted + const catalog = await this.db.findAnalyticsBucketByName(name) + + await BucketDeleted.invoke({ + bucketId: catalog.id, + type: 'ANALYTICS', + tenant: { + ref: this.db.tenantId, + host: this.db.tenantHost, + }, }) } @@ -263,7 +275,10 @@ export class Storage { const count = await this.db.countObjectsInBucket(bucketId, emptyBucketMax + 1) if (count > emptyBucketMax) { - throw ERRORS.UnableToEmptyBucket(bucketId) + throw ERRORS.UnableToEmptyBucket( + bucketId, + 'Unable to empty the bucket because it contains too many objects' + ) } const objects = await this.db.listObjects(bucketId, 'id, name', 1, before) @@ -273,8 +288,12 @@ export class Storage { } // ensure delete permissions - await this.db.testPermission((db) => { - return db.deleteObject(bucketId, objects[0].id!) + await this.db.testPermission(async (db) => { + const deleted = await db.deleteObject(bucketId, objects[0].name) + + if (!deleted) { + throw ERRORS.NoSuchKey(objects[0].name) + } }) // use queue to recursively delete all objects created before the specified time diff --git a/src/storage/uploader.ts b/src/storage/uploader.ts index 4bad81cd3..20a53bb2d 100644 --- a/src/storage/uploader.ts +++ b/src/storage/uploader.ts @@ -1,17 +1,16 @@ +import { ERRORS, StorageBackendError } from '@internal/errors' +import { logger, logSchema } from '@internal/monitoring' +import { fileUploadedSuccess, fileUploadStarted } from '@internal/monitoring/metrics' +import { StorageObjectLocator } from '@storage/locator' import { randomUUID } from 'crypto' import { FastifyRequest } from 'fastify' - -import { ERRORS } from '@internal/errors' -import { FileUploadedSuccess, FileUploadStarted } from '@internal/monitoring/metrics' - +import { PassThrough, Readable } from 'stream' +import { getConfig } from '../config' import { ObjectMetadata, StorageBackendAdapter } from './backend' -import { getFileSizeLimit, isEmptyFolder } from './limits' import { Database } from './database' import { ObjectAdminDelete, ObjectCreatedPostEvent, ObjectCreatedPutEvent } from './events' -import { getConfig } from '../config' -import { logger, logSchema } from '@internal/monitoring' -import { Readable } from 'stream' -import { StorageObjectLocator } from '@storage/locator' +import { getFileSizeLimit, isEmptyFolder } from './limits' +import { validateXRobotsTag } from './validators/x-robots-tag' const { storageS3Bucket, uploadFileSizeLimitStandard } = getConfig() @@ -19,21 +18,43 @@ interface FileUpload { body: Readable mimeType: string cacheControl: string + contentLength?: number + declaredContentLength?: number isTruncated: () => boolean - userMetadata?: Record + xRobotsTag?: string } export interface UploadRequest { bucketId: string objectName: string file: FileUpload + userMetadata?: Record owner?: string isUpsert?: boolean uploadType?: 'standard' | 's3' | 'resumable' signal?: AbortSignal } +export type CanUploadMetadata = Partial> & + Record + +export interface CanUploadOptions { + bucketId: string + objectName: string + owner: string | undefined + isUpsert: boolean | undefined + userMetadata: Record | undefined + metadata: CanUploadMetadata | undefined +} + const MAX_CUSTOM_METADATA_SIZE = 1024 * 1024 +const CLOSE_CONNECTION_ON_ERROR = Symbol('closeConnectionOnError') + +type UploadBodyProxy = PassThrough & { + [CLOSE_CONNECTION_ON_ERROR]?: boolean +} + +type UploadBodySource = FastifyRequest['raw'] /** * Uploader @@ -46,7 +67,7 @@ export class Uploader { private readonly location: StorageObjectLocator ) {} - async canUpload(options: Pick) { + async canUpload(options: CanUploadOptions) { const shouldCreateObject = !options.isUpsert if (shouldCreateObject) { @@ -56,6 +77,8 @@ export class Uploader { name: options.objectName, version: '1', owner: options.owner, + metadata: options.metadata, + user_metadata: options.userMetadata, }) }) } else { @@ -65,6 +88,8 @@ export class Uploader { name: options.objectName, version: '1', owner: options.owner, + metadata: options.metadata, + user_metadata: options.userMetadata, }) }) } @@ -75,10 +100,11 @@ export class Uploader { * We check RLS policies before proceeding * @param options */ - async prepareUpload(options: Omit) { + async prepareUpload(options: CanUploadOptions & { uploadType?: string }) { await this.canUpload(options) - FileUploadStarted.inc({ - is_multipart: Boolean(options.uploadType).toString(), + fileUploadStarted.add(1, { + uploadType: options.uploadType, + tenantId: this.db.tenantId, }) return randomUUID() @@ -91,11 +117,21 @@ export class Uploader { * @param options */ async upload(request: UploadRequest) { - const version = await this.prepareUpload(request) + const file = request.file + const version = await this.prepareUpload({ + bucketId: request.bucketId, + objectName: request.objectName, + owner: request.owner, + isUpsert: request.isUpsert, + userMetadata: request.userMetadata, + metadata: { + mimetype: file.mimeType, + contentLength: file.declaredContentLength ?? file.contentLength, + }, + uploadType: request.uploadType, + }) try { - const file = request.file - const s3Key = this.location.getKeyLocation({ tenantId: this.db.tenantId, bucketId: request.bucketId, @@ -109,9 +145,14 @@ export class Uploader { file.body, file.mimeType, file.cacheControl, - request.signal + request.signal, + file.contentLength ) + if (request.file.xRobotsTag) { + objectMetadata.xRobotsTag = request.file.xRobotsTag + } + if (file.isTruncated()) { throw ERRORS.EntityTooLarge() } @@ -119,18 +160,18 @@ export class Uploader { return this.completeUpload({ ...request, version, - objectMetadata: objectMetadata, - userMetadata: { ...file.userMetadata }, + objectMetadata, + userMetadata: { ...request.userMetadata }, }) } catch (e) { await ObjectAdminDelete.send({ name: request.objectName, bucketId: request.bucketId, tenant: this.db.tenant(), - version: version, + version, reqId: this.db.reqId, }) - throw e + throw shouldCloseConnectionAfterResponse(file.body) ? withConnectionClose(e) : e } } @@ -162,7 +203,13 @@ export class Uploader { userMetadata?: Record }) { try { - return await this.db.asSuperUser().withTransaction(async (db) => { + const db = this.db.asSuperUser() + // Since we have finished uploading the file, + // even if the request is aborted now, we want to complete the DB transaction + const abController = new AbortController() + db.connection.setAbortSignal(abController.signal) + + return await db.withTransaction(async (db) => { await db.waitObjectLock(bucketId, objectName, undefined, { timeout: 5000, }) @@ -191,7 +238,7 @@ export class Uploader { events.push( ObjectAdminDelete.send({ name: objectName, - bucketId: bucketId, + bucketId, tenant: this.db.tenant(), version: currentObj.version, reqId: this.db.reqId, @@ -206,8 +253,8 @@ export class Uploader { .sendWebhook({ tenant: this.db.tenant(), name: objectName, - version: version, - bucketId: bucketId, + version, + bucketId, metadata: objectMetadata, reqId: this.db.reqId, uploadType, @@ -219,7 +266,7 @@ export class Uploader { project: this.db.tenantId, metadata: JSON.stringify({ name: objectName, - bucketId: bucketId, + bucketId, metadata: objectMetadata, reqId: this.db.reqId, uploadType, @@ -230,11 +277,9 @@ export class Uploader { await Promise.all(events) - FileUploadedSuccess.inc({ - is_multipart: uploadType === 'resumable' ? 1 : 0, - is_resumable: uploadType === 'resumable' ? 1 : 0, - is_standard: uploadType === 'standard' ? 1 : 0, - is_s3: uploadType === 's3' ? 1 : 0, + fileUploadedSuccess.add(1, { + uploadType, + tenantId: this.db.tenantId, }) return { obj: newObject, isNew, metadata: objectMetadata } @@ -242,7 +287,7 @@ export class Uploader { } catch (e) { await ObjectAdminDelete.send({ name: objectName, - bucketId: bucketId, + bucketId, tenant: this.db.tenant(), version, reqId: this.db.reqId, @@ -287,6 +332,87 @@ export function validateMimeType(mimeType: string, allowedMimeTypes: string[]) { throw ERRORS.InvalidMimeType(mimeType) } +function getKnownRequestContentLength(request: FastifyRequest): number | undefined { + // Only authenticated aws-chunked S3 requests get a verified decoded length. + const decodedContentLengthHeader = request.streamingSignatureV4 + ? request.headers['x-amz-decoded-content-length'] + : undefined + const contentLengthHeader = decodedContentLengthHeader ?? request.headers['content-length'] + const contentLength = Number(contentLengthHeader) + + if (!Number.isFinite(contentLength) || contentLength < 0) { + return undefined + } + + return contentLength +} + +function createUploadBodyProxy(body: UploadBodySource) { + const proxy = new PassThrough() as UploadBodyProxy + let bodyEnded = false + const destroy = proxy.destroy.bind(proxy) + + proxy.destroy = ((error?: Error) => { + if (error) { + proxy[CLOSE_CONNECTION_ON_ERROR] = true + } + + return destroy(error) + }) as typeof proxy.destroy + + const onBodyError = (err: Error) => { + if (!proxy.destroyed) { + proxy.destroy(err) + } + } + + const onBodyEnd = () => { + bodyEnded = true + } + + const onBodyClose = () => { + if (!bodyEnded && !body.readableEnded && !proxy.destroyed) { + proxy.destroy(new Error('Request stream closed before upload could complete')) + } + } + + const onProxyError = () => { + body.unpipe(proxy) + } + + const cleanup = () => { + body.unpipe(proxy) + body.off('aborted', onBodyClose) + body.off('close', onBodyClose) + body.off('end', onBodyEnd) + body.off('error', onBodyError) + proxy.off('error', onProxyError) + proxy.off('close', cleanup) + } + + body.on('aborted', onBodyClose) + body.on('close', onBodyClose) + body.on('end', onBodyEnd) + body.on('error', onBodyError) + proxy.on('error', onProxyError) + proxy.on('close', cleanup) + body.pipe(proxy) + + return proxy +} + +function shouldCloseConnectionAfterResponse(body: Readable) { + return Boolean((body as UploadBodyProxy)[CLOSE_CONNECTION_ON_ERROR]) +} + +function withConnectionClose(error: unknown) { + if (error instanceof StorageBackendError) { + return error.withConnectionClose() + } + + return StorageBackendError.fromError(error).withConnectionClose() +} + /** * Extracts the file information from the request * @param request @@ -299,13 +425,27 @@ export async function fileUploadFromRequest( allowedMimeTypes?: string[] objectName: string } -): Promise { +): Promise< + FileUpload & { + mimeType: string + maxFileSize: number + userMetadata: Record | undefined + contentLength: number | undefined + declaredContentLength: number | undefined + } +> { const contentType = request.headers['content-type'] + const xRobotsTag = request.headers['x-robots-tag'] as string | undefined + + if (xRobotsTag) { + validateXRobotsTag(xRobotsTag) + } let body: Readable - let userMetadata: Record | undefined + let userMetadata: Record | undefined let mimeType: string let isTruncated: () => boolean + let fileContentLength: number | undefined let maxFileSize = 0 // When is an empty folder we restrict it to 0 bytes @@ -326,13 +466,17 @@ export async function fileUploadFromRequest( /* @ts-expect-error: https://github.com/aws/aws-sdk-js-v3/issues/2085 */ const cacheTime = formData.fields.cacheControl?.value - body = formData.file + const file = formData.file + body = file + // multipart/form-data content-length includes boundary overhead and cannot be trusted as file size, + // so we intentionally leave fileContentLength undefined and let the backend stream via multipart upload. /* @ts-expect-error: https://github.com/aws/aws-sdk-js-v3/issues/2085 */ const customMd = formData.fields.metadata?.value ?? formData.fields.userMetadata?.value /* @ts-expect-error: https://github.com/aws/aws-sdk-js-v3/issues/2085 */ mimeType = formData.fields.contentType?.value || formData.mimetype cacheControl = cacheTime ? `max-age=${cacheTime}` : 'no-cache' - isTruncated = () => formData.file.truncated + // Store file reference to avoid capturing entire formData object in closure + isTruncated = () => file.truncated if ( options.allowedMimeTypes && @@ -349,16 +493,22 @@ export async function fileUploadFromRequest( try { userMetadata = JSON.parse(customMd) - } catch (e) { + } catch { // no-op } } } catch (e) { + if (e instanceof StorageBackendError) { + throw e + } throw ERRORS.NoContentProvided(e as Error) } } else { // just assume it's a binary file - body = request.raw + if (!request.raw || request.raw.closed || request.raw.destroyed || request.raw.readableEnded) { + throw ERRORS.NoContentProvided(new Error('Request stream closed before upload could begin')) + } + mimeType = request.headers['content-type'] || 'application/octet-stream' cacheControl = request.headers['cache-control'] ?? 'no-cache' @@ -375,19 +525,45 @@ export async function fileUploadFromRequest( if (typeof customMd === 'string') { userMetadata = parseUserMetadata(customMd) } - isTruncated = () => { - // @todo more secure to get this from the stream or from s3 in the next step - return Number(request.headers['content-length']) > maxFileSize + + fileContentLength = getKnownRequestContentLength(request) + if (typeof fileContentLength === 'number' && fileContentLength > maxFileSize) { + throw ERRORS.EntityTooLarge() } + + // Known-size binary uploads are rejected before + // reaching the backend when they exceed the limit. + // Unknown-size binary uploads do not have a later truncation signal. + // + // Keep the declared request size separate from the backend upload size: + // request-backed uploads should continue using multipart upstream writes + // instead of direct PutObject, even when the client sent Content-Length. + isTruncated = () => false + body = createUploadBodyProxy(request.raw) + fileContentLength = undefined + } + + // Capture the declared content-length for RLS metadata purposes. + // Request-backed uploads never forward this value to the backend upload path. + const declaredContentLength = getKnownRequestContentLength(request) + + // Detect if the request stream closed before we could pass it to the storage backend + // Without this check, the storage backend (S3) would throw a 500 "Premature close" error + // when attempting to read from the closed stream. We catch this early and return 400. + if (!body || body.closed || body.destroyed || body.readableEnded) { + throw ERRORS.NoContentProvided(new Error('Request stream closed before upload could begin')) } return { body, mimeType, cacheControl, + contentLength: fileContentLength, + declaredContentLength, isTruncated, userMetadata, maxFileSize, + xRobotsTag, } } @@ -395,7 +571,7 @@ export function parseUserMetadata(metadata: string) { try { const json = Buffer.from(metadata, 'base64').toString('utf8') return JSON.parse(json) as Record - } catch (e) { + } catch { // no-op return undefined } diff --git a/src/storage/validators/x-robots-tag.test.ts b/src/storage/validators/x-robots-tag.test.ts new file mode 100644 index 000000000..7de8367ce --- /dev/null +++ b/src/storage/validators/x-robots-tag.test.ts @@ -0,0 +1,303 @@ +import { validateXRobotsTag } from './x-robots-tag' + +describe('validateXRobotsTag', () => { + describe('invalid inputs', () => { + it('should throw error for empty string', () => { + expect(() => validateXRobotsTag('')).toThrow( + 'X-Robots-Tag header value must be a non-empty string' + ) + }) + + it('should throw error for whitespace-only string', () => { + expect(() => validateXRobotsTag(' ')).toThrow( + 'X-Robots-Tag header value must be a non-empty string' + ) + }) + + it('should throw error for non-string value', () => { + expect(() => validateXRobotsTag(null as unknown as string)).toThrow( + 'X-Robots-Tag header value must be a non-empty string' + ) + }) + + it('should throw error for undefined', () => { + expect(() => validateXRobotsTag(undefined as unknown as string)).toThrow( + 'X-Robots-Tag header value must be a non-empty string' + ) + }) + + it('should throw error for empty rule in comma-separated list', () => { + expect(() => validateXRobotsTag('noindex, , nofollow')).toThrow( + 'X-Robots-Tag header contains empty rule' + ) + }) + + it('should throw error for invalid rule', () => { + expect(() => validateXRobotsTag('invalidrule')).toThrow( + 'Invalid X-Robots-Tag rule: "invalidrule"' + ) + }) + }) + + describe('valid simple rules', () => { + it('should accept "all"', () => { + expect(() => validateXRobotsTag('all')).not.toThrow() + }) + + it('should accept "noindex"', () => { + expect(() => validateXRobotsTag('noindex')).not.toThrow() + }) + + it('should accept "nofollow"', () => { + expect(() => validateXRobotsTag('nofollow')).not.toThrow() + }) + + it('should accept "none"', () => { + expect(() => validateXRobotsTag('none')).not.toThrow() + }) + + it('should accept "nosnippet"', () => { + expect(() => validateXRobotsTag('nosnippet')).not.toThrow() + }) + + it('should accept "indexifembedded"', () => { + expect(() => validateXRobotsTag('indexifembedded')).not.toThrow() + }) + + it('should accept "notranslate"', () => { + expect(() => validateXRobotsTag('notranslate')).not.toThrow() + }) + + it('should accept "noimageindex"', () => { + expect(() => validateXRobotsTag('noimageindex')).not.toThrow() + }) + }) + + describe('multiple rules', () => { + it('should accept multiple valid rules separated by commas', () => { + expect(() => validateXRobotsTag('noindex, nofollow')).not.toThrow() + }) + + it('should accept multiple rules with extra whitespace', () => { + expect(() => validateXRobotsTag('noindex, nofollow, noimageindex')).not.toThrow() + }) + + it('should accept rules with trailing comma and whitespace', () => { + expect(() => validateXRobotsTag('noindex, nofollow, ')).not.toThrow() + }) + + it('should accept single rule with trailing comma', () => { + expect(() => validateXRobotsTag('noindex,')).not.toThrow() + }) + + it('should throw for invalid rule in multiple rules', () => { + expect(() => validateXRobotsTag('noindex, invalidrule, nofollow')).toThrow( + 'Invalid X-Robots-Tag rule: "invalidrule"' + ) + }) + }) + + describe('max-snippet parametric rule', () => { + it('should accept valid max-snippet with number', () => { + expect(() => validateXRobotsTag('max-snippet: 50')).not.toThrow() + }) + + it('should accept max-snippet with 0', () => { + expect(() => validateXRobotsTag('max-snippet: 0')).not.toThrow() + }) + + it('should throw for max-snippet with negative number', () => { + expect(() => validateXRobotsTag('max-snippet: -5')).toThrow( + 'X-Robots-Tag "max-snippet" value must be a non-negative number' + ) + }) + + it('should throw for max-snippet with non-numeric value', () => { + expect(() => validateXRobotsTag('max-snippet: abc')).toThrow( + 'X-Robots-Tag "max-snippet" value must be a non-negative number' + ) + }) + + it('should throw for max-snippet without value', () => { + expect(() => validateXRobotsTag('max-snippet:')).toThrow( + 'X-Robots-Tag rule "max-snippet" requires a value' + ) + }) + + it('should throw for max-snippet with whitespace-only value', () => { + expect(() => validateXRobotsTag('max-snippet: ')).toThrow( + 'X-Robots-Tag rule "max-snippet" requires a value' + ) + }) + }) + + describe('max-image-preview parametric rule', () => { + it('should accept "none"', () => { + expect(() => validateXRobotsTag('max-image-preview: none')).not.toThrow() + }) + + it('should accept "standard"', () => { + expect(() => validateXRobotsTag('max-image-preview: standard')).not.toThrow() + }) + + it('should accept "large"', () => { + expect(() => validateXRobotsTag('max-image-preview: large')).not.toThrow() + }) + + it('should throw for invalid value', () => { + expect(() => validateXRobotsTag('max-image-preview: invalid')).toThrow( + 'X-Robots-Tag "max-image-preview" value must be one of: none, standard, large' + ) + }) + + it('should throw for missing value', () => { + expect(() => validateXRobotsTag('max-image-preview:')).toThrow( + 'X-Robots-Tag rule "max-image-preview" requires a value' + ) + }) + }) + + describe('max-video-preview parametric rule', () => { + it('should accept positive number', () => { + expect(() => validateXRobotsTag('max-video-preview: 30')).not.toThrow() + }) + + it('should accept 0', () => { + expect(() => validateXRobotsTag('max-video-preview: 0')).not.toThrow() + }) + + it('should accept -1 (no limit)', () => { + expect(() => validateXRobotsTag('max-video-preview: -1')).not.toThrow() + }) + + it('should throw for number less than -1', () => { + expect(() => validateXRobotsTag('max-video-preview: -2')).toThrow( + 'X-Robots-Tag "max-video-preview" value must be a number >= -1' + ) + }) + + it('should throw for non-numeric value', () => { + expect(() => validateXRobotsTag('max-video-preview: abc')).toThrow( + 'X-Robots-Tag "max-video-preview" value must be a number >= -1' + ) + }) + + it('should throw for missing value', () => { + expect(() => validateXRobotsTag('max-video-preview:')).toThrow( + 'X-Robots-Tag rule "max-video-preview" requires a value' + ) + }) + }) + + describe('unavailable_after parametric rule', () => { + it('should accept valid RFC 822 date', () => { + expect(() => + validateXRobotsTag('unavailable_after: Wed, 03 Dec 2025 13:09:53 GMT') + ).not.toThrow() + }) + + it('should accept valid ISO 8601 date', () => { + expect(() => validateXRobotsTag('unavailable_after: 2025-12-03T13:09:53Z')).not.toThrow() + }) + + it('should accept other valid date format', () => { + expect(() => validateXRobotsTag('unavailable_after: 2025-12-03')).not.toThrow() + }) + + it('should accept RFC 822 date followed by another rule', () => { + expect(() => + validateXRobotsTag('unavailable_after: Wed, 03 Dec 2025 13:09:53 GMT, noindex') + ).not.toThrow() + }) + + it('should throw for invalid date', () => { + expect(() => validateXRobotsTag('unavailable_after: not-a-date')).toThrow( + 'X-Robots-Tag "unavailable_after" value must be a valid date' + ) + }) + + it('should throw for missing value', () => { + expect(() => validateXRobotsTag('unavailable_after:')).toThrow( + 'X-Robots-Tag rule "unavailable_after" requires a value' + ) + }) + }) + + describe('user agent specific rules', () => { + it('should accept single rule for specific user agent', () => { + expect(() => validateXRobotsTag('googlebot: noindex')).not.toThrow() + }) + + it('should accept multiple rules for specific user agent', () => { + expect(() => validateXRobotsTag('googlebot: noindex, nofollow')).not.toThrow() + }) + + it('should accept multiple user agents with different rules', () => { + expect(() => + validateXRobotsTag('BadBot: noindex, nofollow, googlebot: nofollow') + ).not.toThrow() + }) + + it('should throw for user agent with no rules', () => { + expect(() => validateXRobotsTag('googlebot:')).toThrow( + 'X-Robots-Tag user agent "googlebot" has no rules specified' + ) + }) + + it('should throw for user agent with whitespace-only rules', () => { + expect(() => validateXRobotsTag('googlebot: ')).toThrow( + 'X-Robots-Tag user agent "googlebot" has no rules specified' + ) + }) + + it('should throw for invalid rule in user agent rules', () => { + expect(() => validateXRobotsTag('googlebot: invalidrule')).toThrow( + 'Invalid X-Robots-Tag rule: "invalidrule"' + ) + }) + }) + + describe('invalid parametric rule names', () => { + it('should throw for unknown parametric rule', () => { + // When an unknown parametric-looking rule is provided, it's treated as a user agent + // and the value is validated as a rule, which should fail + expect(() => validateXRobotsTag('unknown-rule: invalidvalue')).toThrow( + 'Invalid X-Robots-Tag rule: "invalidvalue"' + ) + }) + }) + + describe('complex mixed rules', () => { + it('should accept mix of simple and parametric rules', () => { + expect(() => validateXRobotsTag('noindex, max-snippet: 100')).not.toThrow() + }) + + it('should accept mix of user agent and parametric rules', () => { + expect(() => validateXRobotsTag('googlebot: noindex, max-snippet: 50')).not.toThrow() + }) + + it('should accept complex real-world example', () => { + expect(() => + validateXRobotsTag('noindex, nofollow, max-snippet: 100, max-image-preview: large') + ).not.toThrow() + }) + }) + + describe('whitespace handling', () => { + it('should accept rules with excessive whitespace between rules', () => { + expect(() => validateXRobotsTag('noindex, nofollow, noimageindex')).not.toThrow() + }) + + it('should accept parametric rules with whitespace after colon', () => { + expect(() => validateXRobotsTag('max-snippet: 50')).not.toThrow() + }) + + it('should reject rules with spaces in rule names', () => { + expect(() => validateXRobotsTag('no index')).toThrow('Invalid X-Robots-Tag rule: "no index"') + }) + + it('should accept rules with leading and trailing whitespace', () => { + expect(() => validateXRobotsTag(' noindex, nofollow ')).not.toThrow() + }) + }) +}) diff --git a/src/storage/validators/x-robots-tag.ts b/src/storage/validators/x-robots-tag.ts new file mode 100644 index 000000000..5f3ce914f --- /dev/null +++ b/src/storage/validators/x-robots-tag.ts @@ -0,0 +1,188 @@ +import { ERRORS } from '@internal/errors' + +const SIMPLE_RULES = [ + 'all', + 'noindex', + 'nofollow', + 'none', + 'nosnippet', + 'indexifembedded', + 'notranslate', + 'noimageindex', +] as const + +const PARAMETRIC_RULES = [ + 'max-snippet', + 'max-image-preview', + 'max-video-preview', + 'unavailable_after', +] as const + +const simpleRulesPattern = SIMPLE_RULES.join('|') +const parametricRulesPattern = PARAMETRIC_RULES.join('|') +const SIMPLE_RULE_REGEX = new RegExp(`^(${simpleRulesPattern})$`) +const PARAMETRIC_RULE_REGEX = new RegExp(`^(${parametricRulesPattern}):\\s*(.*)$`) +const PARAMETRIC_RULE_START_REGEX = new RegExp(`^(${parametricRulesPattern}):`) +const VALID_IMAGE_PREVIEW_VALUES = new Set(['none', 'standard', 'large']) + +/** + * Validates the X-Robots-Tag header value according to MDN specification + * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Robots-Tag + * + * @param value - The X-Robots-Tag header value to validate + * @throws {Error} If the header value is invalid + */ +export function validateXRobotsTag(value: string): void { + if (!value || typeof value !== 'string') { + throw ERRORS.InvalidXRobotsTag('X-Robots-Tag header value must be a non-empty string') + } + + const trimmedValue = value.trim() + if (!trimmedValue) { + throw ERRORS.InvalidXRobotsTag('X-Robots-Tag header value must be a non-empty string') + } + + const parts = splitRules(trimmedValue) + + for (const part of parts) { + if (!part) { + throw ERRORS.InvalidXRobotsTag('X-Robots-Tag header contains empty rule') + } + + // Check if this is a parametric rule + const parametricMatch = part.match(PARAMETRIC_RULE_REGEX) + if (parametricMatch) { + const [, ruleName, ruleValue] = parametricMatch + validateParametricRule(ruleName, ruleValue.trim(), VALID_IMAGE_PREVIEW_VALUES) + continue + } + + // Check if this is a simple rule + if (SIMPLE_RULE_REGEX.test(part)) { + continue + } + + // Check if this has a colon (could be user agent prefix) + const colonIndex = part.indexOf(':') + if (colonIndex !== -1) { + const beforeColon = part.substring(0, colonIndex).trim() + const afterColon = part.substring(colonIndex + 1).trim() + + if (!afterColon) { + throw ERRORS.InvalidXRobotsTag( + `X-Robots-Tag user agent "${beforeColon}" has no rules specified` + ) + } + + // Recursively validate user agent rules + validateXRobotsTag(afterColon) + continue + } + + throw ERRORS.InvalidXRobotsTag(`Invalid X-Robots-Tag rule: "${part}"`) + } +} + +/** + * Splits rules by comma, handling parametric rules with dates that contain commas + */ +function splitRules(value: string): string[] { + const parts: string[] = [] + let remaining = value + + while (remaining) { + remaining = remaining.trim() + if (!remaining) break + + const match = remaining.match(PARAMETRIC_RULE_START_REGEX) + if (match) { + const ruleName = match[1] + + // For unavailable_after, extract date value (may contain commas) + if (ruleName === 'unavailable_after') { + // Build regex to find end of date by looking for comma + known rule or user agent + const endPattern = new RegExp( + `unavailable_after:\\s*(.+?)(?:,\\s*(?:${simpleRulesPattern}|${parametricRulesPattern}|[a-zA-Z0-9_-]+:)|$)` + ) + const dateEndMatch = remaining.match(endPattern) + + if (dateEndMatch) { + const fullRule = `unavailable_after: ${dateEndMatch[1].trim()}` + parts.push(fullRule) + remaining = remaining.substring(fullRule.length).replace(/^,\s*/, '').trim() + } else { + parts.push(remaining) + remaining = '' + } + continue + } + } + + // Default: split by comma (for other parametric rules and simple rules) + const nextComma = remaining.indexOf(',') + if (nextComma === -1) { + parts.push(remaining) + remaining = '' + } else { + parts.push(remaining.substring(0, nextComma).trim()) + remaining = remaining.substring(nextComma + 1).trim() + } + } + + return parts +} + +/** + * Validates a parametric rule value + */ +function validateParametricRule( + ruleName: string, + ruleValue: string, + validImagePreviewValues: Set +): void { + if (!ruleValue) { + throw ERRORS.InvalidXRobotsTag(`X-Robots-Tag rule "${ruleName}" requires a value`) + } + + switch (ruleName) { + case 'max-snippet': { + const num = parseInt(ruleValue, 10) + if (isNaN(num) || num < 0) { + throw ERRORS.InvalidXRobotsTag( + `X-Robots-Tag "max-snippet" value must be a non-negative number, got: "${ruleValue}"` + ) + } + break + } + + case 'max-image-preview': { + if (!validImagePreviewValues.has(ruleValue)) { + throw ERRORS.InvalidXRobotsTag( + `X-Robots-Tag "max-image-preview" value must be one of: none, standard, large, got: "${ruleValue}"` + ) + } + break + } + + case 'max-video-preview': { + const num = parseInt(ruleValue, 10) + if (isNaN(num) || num < -1) { + throw ERRORS.InvalidXRobotsTag( + `X-Robots-Tag "max-video-preview" value must be a number >= -1, got: "${ruleValue}"` + ) + } + break + } + + case 'unavailable_after': { + // Check if it's a valid date string (try parsing it) + const date = new Date(ruleValue) + if (isNaN(date.getTime())) { + throw ERRORS.InvalidXRobotsTag( + `X-Robots-Tag "unavailable_after" value must be a valid date, got: "${ruleValue}"` + ) + } + break + } + } +} diff --git a/src/test/admin-migrations-disabled.test.ts b/src/test/admin-migrations-disabled.test.ts new file mode 100644 index 000000000..9c51d14a2 --- /dev/null +++ b/src/test/admin-migrations-disabled.test.ts @@ -0,0 +1,53 @@ +import type { FastifyInstance } from 'fastify' +import { getConfig, mergeConfig } from '../config' +import { createAdminApp } from './common' + +getConfig() + +type DisabledRouteCase = { + method: 'DELETE' | 'GET' | 'POST' + url: string + payload?: Record +} + +const disabledRouteCases: DisabledRouteCase[] = [ + { method: 'POST', url: '/migrations/migrate/fleet' }, + { + method: 'POST', + url: '/migrations/reset/fleet', + payload: { untilMigration: 'storage-schema' }, + }, + { method: 'GET', url: '/migrations/active' }, + { method: 'DELETE', url: '/migrations/active' }, + { method: 'GET', url: '/migrations/progress' }, + { method: 'GET', url: '/migrations/failed' }, +] + +let adminApp: FastifyInstance + +describe('Admin migrations routes with queue disabled', () => { + beforeAll(async () => { + mergeConfig({ + pgQueueEnable: false, + }) + adminApp = await createAdminApp() + }) + + afterAll(async () => { + await adminApp.close() + }) + + for (const { method, url, payload } of disabledRouteCases) { + test(`returns 400 for ${method} ${url} when queue is disabled`, async () => { + const response = await adminApp.inject({ + method, + url, + payload, + headers: { apikey: process.env.ADMIN_API_KEYS }, + }) + + expect(response.statusCode).toBe(400) + expect(response.json()).toEqual({ message: 'Queue is not enabled' }) + }) + } +}) diff --git a/src/test/admin-migrations.test.ts b/src/test/admin-migrations.test.ts new file mode 100644 index 000000000..c44640834 --- /dev/null +++ b/src/test/admin-migrations.test.ts @@ -0,0 +1,607 @@ +vi.mock('@internal/database/migrations', async () => { + const actual = await vi.importActual( + '@internal/database/migrations' + ) + return { + ...actual, + resetMigrationsOnTenants: vi.fn(), + resetMigration: vi.fn(), + runMigrationsOnAllTenants: vi.fn(), + runMigrationsOnTenant: vi.fn(), + } +}) + +import * as migrations from '@internal/database/migrations' +import { DBMigration } from '@internal/database/migrations' +import { randomUUID } from 'crypto' +import type { FastifyInstance } from 'fastify' +import { mergeConfig } from '../config' +import { multitenantKnex } from '../internal/database/multitenant-db' +import { PG_BOSS_SCHEMA, Queue } from '../internal/queue/queue' +import { RunMigrationsOnTenants } from '../storage/events/migrations/run-migrations' +import { createAdminApp } from './common' + +const tenantId = 'admin-migrations-test-tenant' +const createdJobIds = new Set() +const createdTenantIds = new Set() +const pgBossJobTable = `${PG_BOSS_SCHEMA}.job` +const headers = { + apikey: process.env.ADMIN_API_KEYS, +} + +const tenantPayload = { + anonKey: 'anon-key', + databaseUrl: 'postgres://tenant-db', + jwtSecret: 'jwt-secret', + serviceKey: 'service-key', +} + +function trackJobId(jobId: string) { + createdJobIds.add(jobId) + return jobId +} + +let adminApp: FastifyInstance + +async function createTenant(currentTenantId: string) { + createdTenantIds.add(currentTenantId) + const response = await adminApp.inject({ + method: 'POST', + url: `/tenants/${currentTenantId}`, + payload: tenantPayload, + headers, + }) + + expect(response.statusCode).toBe(201) +} + +describe('Admin migrations routes', () => { + beforeAll(async () => { + mergeConfig({ + pgQueueEnable: true, + }) + await migrations.runMultitenantMigrations() + await multitenantKnex.raw(`CREATE SCHEMA IF NOT EXISTS ${PG_BOSS_SCHEMA}`) + await multitenantKnex.raw(` + CREATE TABLE IF NOT EXISTS ${pgBossJobTable} ( + id uuid PRIMARY KEY, + name text NOT NULL, + state text NOT NULL, + created_on timestamptz NOT NULL DEFAULT now(), + data jsonb NOT NULL DEFAULT '{}'::jsonb + ) + `) + adminApp = await createAdminApp() + }) + + afterEach(async () => { + vi.restoreAllMocks() + vi.clearAllMocks() + + if (createdJobIds.size > 0) { + await multitenantKnex(pgBossJobTable).whereIn('id', Array.from(createdJobIds)).delete() + createdJobIds.clear() + } + + const tenantIdsToDelete = new Set([tenantId, ...createdTenantIds]) + createdTenantIds.clear() + + for (const currentTenantId of tenantIdsToDelete) { + await adminApp.inject({ + method: 'DELETE', + url: `/tenants/${currentTenantId}`, + headers, + }) + } + }) + + afterAll(async () => { + await adminApp?.close() + await multitenantKnex.destroy() + }) + + test('rejects invalid markCompletedTillMigration for fleet reset', async () => { + const resetSpy = vi.mocked(migrations.resetMigrationsOnTenants).mockResolvedValue(undefined) + + const response = await adminApp.inject({ + method: 'POST', + url: '/migrations/reset/fleet', + payload: { + untilMigration: 'storage-schema' satisfies keyof typeof DBMigration, + markCompletedTillMigration: 'not-a-real-migration', + }, + headers, + }) + + expect(response.statusCode).toBe(400) + expect(JSON.parse(response.body)).toEqual({ message: 'Invalid migration' }) + expect(resetSpy).not.toHaveBeenCalled() + }) + + test('rejects invalid markCompletedTillMigration for tenant reset', async () => { + await createTenant(tenantId) + + const resetSpy = vi.mocked(migrations.resetMigration).mockResolvedValue(false) + + const response = await adminApp.inject({ + method: 'POST', + url: `/tenants/${tenantId}/migrations/reset`, + payload: { + untilMigration: 'storage-schema' satisfies keyof typeof DBMigration, + markCompletedTillMigration: 'not-a-real-migration', + }, + headers, + }) + + expect(response.statusCode).toBe(400) + expect(JSON.parse(response.body)).toEqual({ message: 'Invalid migration' }) + expect(resetSpy).not.toHaveBeenCalled() + }) + + test('accepts untilMigration that maps to numeric id 0 for fleet reset', async () => { + const resetSpy = vi.mocked(migrations.resetMigrationsOnTenants).mockResolvedValue(undefined) + + const response = await adminApp.inject({ + method: 'POST', + url: '/migrations/reset/fleet', + payload: { + untilMigration: 'create-migrations-table' satisfies keyof typeof DBMigration, + }, + headers, + }) + + expect(response.statusCode).toBe(200) + expect(JSON.parse(response.body)).toEqual({ message: 'Migrations scheduled' }) + expect(resetSpy).toHaveBeenCalledWith({ + till: 'create-migrations-table', + markCompletedTillMigration: undefined, + signal: expect.any(AbortSignal), + }) + }) + + test('manual tenant migration updates the tenant migration state', async () => { + const migrationTenantId = `admin-migrations-state-${randomUUID().slice(0, 8)}` + const latestMigration = await migrations.lastLocalMigrationName() + + await createTenant(migrationTenantId) + + await multitenantKnex('tenants').where({ id: migrationTenantId }).update({ + migrations_version: null, + migrations_status: null, + }) + + const migrateResponse = await adminApp.inject({ + method: 'POST', + url: `/tenants/${migrationTenantId}/migrations`, + headers, + }) + + expect(migrateResponse.statusCode).toBe(200) + expect(JSON.parse(migrateResponse.body)).toEqual({ migrated: true }) + + const getResponse = await adminApp.inject({ + method: 'GET', + url: `/tenants/${migrationTenantId}/migrations`, + headers, + }) + + expect(getResponse.statusCode).toBe(200) + expect(JSON.parse(getResponse.body)).toEqual({ + isLatest: true, + migrationsVersion: latestMigration, + migrationsStatus: 'COMPLETED', + }) + + await expect( + multitenantKnex('tenants') + .select('migrations_version', 'migrations_status') + .where({ id: migrationTenantId }) + .first() + ).resolves.toEqual({ + migrations_version: latestMigration, + migrations_status: 'COMPLETED', + }) + }) + + test('manual tenant migration records the frozen migration target', async () => { + const migrationTenantId = `admin-migrations-freeze-${randomUUID().slice(0, 8)}` + const frozenMigration = 'create-migrations-table' satisfies keyof typeof DBMigration + let isolatedAdminApp: FastifyInstance | undefined + let isolatedMultitenantKnex: typeof multitenantKnex | undefined + + await createTenant(migrationTenantId) + + await multitenantKnex('tenants').where({ id: migrationTenantId }).update({ + migrations_version: null, + migrations_status: null, + }) + + vi.resetModules() + + try { + const config = await import('../config') + config.getConfig({ reload: true }) + config.mergeConfig({ + pgQueueEnable: true, + dbMigrationFreezeAt: frozenMigration, + }) + + const isolatedMigrations = await import('@internal/database/migrations') + vi.mocked(isolatedMigrations.runMigrationsOnTenant).mockResolvedValue(undefined) + + const multitenantDb = await import('../internal/database/multitenant-db') + isolatedMultitenantKnex = multitenantDb.multitenantKnex + + const adminAppModule = await import('../admin-app') + isolatedAdminApp = adminAppModule.default({}) + + if (!isolatedAdminApp) { + throw new Error('Failed to build isolated admin app') + } + + const migrateResponse = await isolatedAdminApp.inject({ + method: 'POST', + url: `/tenants/${migrationTenantId}/migrations`, + headers, + }) + + expect(migrateResponse.statusCode).toBe(200) + expect(JSON.parse(migrateResponse.body)).toEqual({ migrated: true }) + + const getResponse = await isolatedAdminApp.inject({ + method: 'GET', + url: `/tenants/${migrationTenantId}/migrations`, + headers, + }) + + expect(getResponse.statusCode).toBe(200) + expect(JSON.parse(getResponse.body)).toEqual({ + isLatest: true, + migrationsVersion: frozenMigration, + migrationsStatus: 'COMPLETED', + }) + + await expect( + multitenantKnex('tenants') + .select('migrations_version', 'migrations_status') + .where({ id: migrationTenantId }) + .first() + ).resolves.toEqual({ + migrations_version: frozenMigration, + migrations_status: 'COMPLETED', + }) + } finally { + await isolatedAdminApp?.close() + if (isolatedMultitenantKnex && isolatedMultitenantKnex !== multitenantKnex) { + await isolatedMultitenantKnex.destroy() + } + vi.resetModules() + } + }) + + test('lists active fleet migration jobs from the current pg-boss queue name', async () => { + const fleetTenantId = `admin-migrations-fleet-${randomUUID().slice(0, 8)}` + const jobId = trackJobId(randomUUID()) + const wrongQueueJobId = trackJobId(randomUUID()) + const wrongStateJobId = trackJobId(randomUUID()) + + await createTenant(fleetTenantId) + + await multitenantKnex(pgBossJobTable).insert({ + id: jobId, + name: RunMigrationsOnTenants.getQueueName(), + state: 'active', + data: { + tenantId: fleetTenantId, + }, + }) + await multitenantKnex(pgBossJobTable).insert({ + id: wrongQueueJobId, + name: 'another-queue', + state: 'active', + data: { + tenantId: fleetTenantId, + }, + }) + await multitenantKnex(pgBossJobTable).insert({ + id: wrongStateJobId, + name: RunMigrationsOnTenants.getQueueName(), + state: 'created', + data: { + tenantId: fleetTenantId, + }, + }) + + const response = await adminApp.inject({ + method: 'GET', + url: '/migrations/active', + headers, + }) + + expect(response.statusCode).toBe(200) + const body = JSON.parse(response.body) + expect(body).toHaveLength(1) + expect(body).toEqual([ + expect.objectContaining({ + id: jobId, + name: RunMigrationsOnTenants.getQueueName(), + state: 'active', + }), + ]) + }) + + test('lists tenant migration jobs from the current pg-boss schema and queue name', async () => { + const jobTenantId = `admin-migrations-tenant-${randomUUID().slice(0, 8)}` + const otherTenantId = `admin-migrations-other-${randomUUID().slice(0, 8)}` + const olderJobId = trackJobId(randomUUID()) + const newerJobId = trackJobId(randomUUID()) + const otherTenantJobId = trackJobId(randomUUID()) + const wrongQueueJobId = trackJobId(randomUUID()) + + await createTenant(jobTenantId) + await createTenant(otherTenantId) + + await multitenantKnex(pgBossJobTable).insert({ + id: olderJobId, + name: RunMigrationsOnTenants.getQueueName(), + state: 'active', + created_on: new Date('2026-03-25T10:00:00.000Z'), + data: { + tenant: { + ref: jobTenantId, + }, + tenantId: jobTenantId, + }, + }) + await multitenantKnex(pgBossJobTable).insert({ + id: newerJobId, + name: RunMigrationsOnTenants.getQueueName(), + state: 'active', + created_on: new Date('2026-03-25T11:00:00.000Z'), + data: { + tenant: { + ref: jobTenantId, + }, + tenantId: jobTenantId, + }, + }) + await multitenantKnex(pgBossJobTable).insert({ + id: otherTenantJobId, + name: RunMigrationsOnTenants.getQueueName(), + state: 'active', + data: { + tenant: { + ref: otherTenantId, + }, + tenantId: otherTenantId, + }, + }) + await multitenantKnex(pgBossJobTable).insert({ + id: wrongQueueJobId, + name: 'another-queue', + state: 'active', + data: { + tenant: { + ref: jobTenantId, + }, + tenantId: jobTenantId, + }, + }) + + const response = await adminApp.inject({ + method: 'GET', + url: `/tenants/${jobTenantId}/migrations/jobs`, + headers, + }) + + expect(response.statusCode).toBe(200) + const body = JSON.parse(response.body) + expect(body).toHaveLength(2) + expect(body).toEqual([ + expect.objectContaining({ + id: newerJobId, + name: RunMigrationsOnTenants.getQueueName(), + }), + expect.objectContaining({ + id: olderJobId, + name: RunMigrationsOnTenants.getQueueName(), + }), + ]) + }) + + test('returns queue progress for the current migration queue', async () => { + const getQueueSize = vi.fn().mockResolvedValue(7) + vi.spyOn(Queue, 'getInstance').mockReturnValue({ + getQueueSize, + } as never) + + const response = await adminApp.inject({ + method: 'GET', + url: '/migrations/progress', + headers, + }) + + expect(response.statusCode).toBe(200) + expect(JSON.parse(response.body)).toEqual({ remaining: 7 }) + expect(getQueueSize).toHaveBeenCalledWith(RunMigrationsOnTenants.getQueueName()) + }) + + test('lists failed tenants and paginates by cursor', async () => { + const firstFailedTenantId = `admin-migrations-failed-${randomUUID().slice(0, 8)}` + const secondFailedTenantId = `admin-migrations-failed-${randomUUID().slice(0, 8)}` + const healthyTenantId = `admin-migrations-ok-${randomUUID().slice(0, 8)}` + + await createTenant(firstFailedTenantId) + await createTenant(secondFailedTenantId) + await createTenant(healthyTenantId) + + await multitenantKnex('tenants').where({ id: firstFailedTenantId }).update({ + migrations_status: 'FAILED', + }) + await multitenantKnex('tenants').where({ id: secondFailedTenantId }).update({ + migrations_status: 'FAILED', + }) + await multitenantKnex('tenants').where({ id: healthyTenantId }).update({ + migrations_status: 'COMPLETED', + }) + + const firstPageResponse = await adminApp.inject({ + method: 'GET', + url: '/migrations/failed', + headers, + }) + + expect(firstPageResponse.statusCode).toBe(200) + const firstPageBody = JSON.parse(firstPageResponse.body) + expect(firstPageBody.data).toHaveLength(2) + expect(firstPageBody.data.map((tenant: { id: string }) => tenant.id)).toEqual([ + firstFailedTenantId, + secondFailedTenantId, + ]) + + const secondPageResponse = await adminApp.inject({ + method: 'GET', + url: `/migrations/failed?cursor=${firstPageBody.data[0].cursor_id}`, + headers, + }) + + expect(secondPageResponse.statusCode).toBe(200) + expect(JSON.parse(secondPageResponse.body)).toEqual({ + next_cursor_id: firstPageBody.data[1].cursor_id, + data: [ + expect.objectContaining({ + id: secondFailedTenantId, + cursor_id: firstPageBody.data[1].cursor_id, + }), + ], + }) + }) + + test('marks only active fleet migration jobs from the current queue as completed', async () => { + const matchingJobId = trackJobId(randomUUID()) + const wrongQueueJobId = trackJobId(randomUUID()) + const wrongStateJobId = trackJobId(randomUUID()) + + await multitenantKnex(pgBossJobTable).insert([ + { + id: matchingJobId, + name: RunMigrationsOnTenants.getQueueName(), + state: 'active', + data: {}, + }, + { + id: wrongQueueJobId, + name: 'another-queue', + state: 'active', + data: {}, + }, + { + id: wrongStateJobId, + name: RunMigrationsOnTenants.getQueueName(), + state: 'created', + data: {}, + }, + ]) + + const response = await adminApp.inject({ + method: 'DELETE', + url: '/migrations/active', + headers, + }) + + expect(response.statusCode).toBe(200) + expect(JSON.parse(response.body)).toBe(1) + + const rows = await multitenantKnex(pgBossJobTable) + .select('id', 'state') + .whereIn('id', [matchingJobId, wrongQueueJobId, wrongStateJobId]) + const statesById = Object.fromEntries(rows.map((row) => [row.id, row.state])) + + expect(statesById).toEqual({ + [matchingJobId]: 'completed', + [wrongQueueJobId]: 'active', + [wrongStateJobId]: 'created', + }) + }) + + test('deletes only tenant migration jobs from the current queue', async () => { + const tenantWithJobsId = `admin-migrations-delete-${randomUUID().slice(0, 8)}` + const otherTenantId = `admin-migrations-delete-other-${randomUUID().slice(0, 8)}` + const matchingJobId = trackJobId(randomUUID()) + const otherTenantJobId = trackJobId(randomUUID()) + const wrongQueueJobId = trackJobId(randomUUID()) + + await createTenant(tenantWithJobsId) + await createTenant(otherTenantId) + + await multitenantKnex(pgBossJobTable).insert([ + { + id: matchingJobId, + name: RunMigrationsOnTenants.getQueueName(), + state: 'active', + data: { + tenant: { + ref: tenantWithJobsId, + }, + tenantId: tenantWithJobsId, + }, + }, + { + id: otherTenantJobId, + name: RunMigrationsOnTenants.getQueueName(), + state: 'active', + data: { + tenant: { + ref: otherTenantId, + }, + tenantId: otherTenantId, + }, + }, + { + id: wrongQueueJobId, + name: 'another-queue', + state: 'active', + data: { + tenant: { + ref: tenantWithJobsId, + }, + tenantId: tenantWithJobsId, + }, + }, + ]) + + const response = await adminApp.inject({ + method: 'DELETE', + url: `/tenants/${tenantWithJobsId}/migrations/jobs`, + headers, + }) + + expect(response.statusCode).toBe(200) + expect(JSON.parse(response.body)).toBe(1) + + const rows = await multitenantKnex(pgBossJobTable) + .select('id', 'name') + .whereIn('id', [matchingJobId, otherTenantJobId, wrongQueueJobId]) + const namesById = Object.fromEntries(rows.map((row) => [row.id, row.name])) + + expect(namesById).toEqual({ + [otherTenantJobId]: RunMigrationsOnTenants.getQueueName(), + [wrongQueueJobId]: 'another-queue', + }) + }) + + test('returns 0 when deleting tenant migration jobs for a tenant with no matching jobs', async () => { + const tenantWithoutJobsId = `admin-migrations-empty-${randomUUID().slice(0, 8)}` + + await createTenant(tenantWithoutJobsId) + + const response = await adminApp.inject({ + method: 'DELETE', + url: `/tenants/${tenantWithoutJobsId}/migrations/jobs`, + headers, + }) + + expect(response.statusCode).toBe(200) + expect(JSON.parse(response.body)).toBe(0) + }) +}) diff --git a/src/test/admin-orphan-objects.test.ts b/src/test/admin-orphan-objects.test.ts new file mode 100644 index 000000000..33bf4adde --- /dev/null +++ b/src/test/admin-orphan-objects.test.ts @@ -0,0 +1,70 @@ +import * as migrations from '@internal/database/migrations' +import { multitenantKnex } from '@internal/database/multitenant-db' +import { adminApp } from './common' + +describe('admin orphan-objects routes', () => { + beforeAll(async () => { + await migrations.runMultitenantMigrations() + }) + + afterAll(async () => { + await adminApp.close() + await multitenantKnex.destroy() + }) + + describe('GET /tenants/:tenantId/buckets/:bucketId/orphan-objects', () => { + it('returns 400 when the before query parameter is not a valid date', async () => { + const response = await adminApp.inject({ + method: 'GET', + url: '/tenants/bjhaohmqunupljrqypxz/buckets/bucket2/orphan-objects?before=not-a-date', + headers: { + apikey: process.env.ADMIN_API_KEYS!, + }, + }) + + expect(response.statusCode).toBe(400) + expect(JSON.parse(response.body).error).toBe('Invalid date format') + }) + }) + + describe('DELETE /tenants/:tenantId/buckets/:bucketId/orphan-objects', () => { + it('returns 400 when the before body field is not a valid date', async () => { + const response = await adminApp.inject({ + method: 'DELETE', + url: '/tenants/bjhaohmqunupljrqypxz/buckets/bucket2/orphan-objects', + headers: { + apikey: process.env.ADMIN_API_KEYS!, + 'content-type': 'application/json', + }, + payload: JSON.stringify({ + deleteDbKeys: true, + deleteS3Keys: false, + before: 'not-a-date', + }), + }) + + expect(response.statusCode).toBe(400) + expect(JSON.parse(response.body).error).toBe('Invalid date format') + }) + + it('returns 400 when neither deleteDbKeys nor deleteS3Keys is set', async () => { + const response = await adminApp.inject({ + method: 'DELETE', + url: '/tenants/bjhaohmqunupljrqypxz/buckets/bucket2/orphan-objects', + headers: { + apikey: process.env.ADMIN_API_KEYS!, + 'content-type': 'application/json', + }, + payload: JSON.stringify({ + deleteDbKeys: false, + deleteS3Keys: false, + }), + }) + + expect(response.statusCode).toBe(400) + expect(JSON.parse(response.body).error).toContain( + 'At least one of deleteDbKeys or deleteS3Keys' + ) + }) + }) +}) diff --git a/src/test/admin-tenants.test.ts b/src/test/admin-tenants.test.ts new file mode 100644 index 000000000..33bd5de94 --- /dev/null +++ b/src/test/admin-tenants.test.ts @@ -0,0 +1,29 @@ +import * as migrations from '@internal/database/migrations' +import { multitenantKnex } from '@internal/database/multitenant-db' +import { adminApp } from './common' + +describe('admin tenant delete route', () => { + beforeAll(async () => { + await migrations.runMultitenantMigrations() + }) + + afterAll(async () => { + await adminApp.close() + await multitenantKnex.destroy() + }) + + it('accepts an empty json delete request', async () => { + const response = await adminApp.inject({ + method: 'DELETE', + url: '/tenants/abc', + headers: { + apikey: process.env.ADMIN_API_KEYS!, + 'content-type': 'application/json', + }, + payload: '', + }) + + expect(response.statusCode).toBe(204) + expect(response.body).toBe('') + }) +}) diff --git a/src/test/app.test.ts b/src/test/app.test.ts new file mode 100644 index 000000000..5c95614c6 --- /dev/null +++ b/src/test/app.test.ts @@ -0,0 +1,85 @@ +import net, { AddressInfo } from 'node:net' +import { FastifyInstance } from 'fastify' +import app from '../app' +import { getConfig } from '../config' + +const { serviceKeyAsync } = getConfig() + +type RawHttpResponse = { + body: string + statusLine: string +} + +function sendRawRequest(port: number, rawRequest: string): Promise { + return new Promise((resolve, reject) => { + const socket = net.createConnection({ host: '127.0.0.1', port }, () => { + socket.end(rawRequest) + }) + + let response = '' + socket.setEncoding('utf8') + socket.setTimeout(5000) + socket.on('data', (chunk) => { + response += chunk + }) + socket.on('end', () => { + const splitIndex = response.indexOf('\r\n\r\n') + const rawHeaders = splitIndex === -1 ? response : response.slice(0, splitIndex) + const body = splitIndex === -1 ? '' : response.slice(splitIndex + 4) + + resolve({ + body, + statusLine: rawHeaders.split('\r\n')[0], + }) + }) + socket.on('error', reject) + socket.on('timeout', () => { + socket.destroy() + reject(new Error('Timed out waiting for raw HTTP response')) + }) + }) +} + +describe('app request parsing', () => { + let appInstance: FastifyInstance + let port: number + + beforeEach(async () => { + appInstance = app() + await appInstance.listen({ host: '127.0.0.1', port: 0 }) + + const address = appInstance.server.address() + if (!address || typeof address === 'string') { + throw new Error('Expected Fastify to listen on a TCP port') + } + + port = (address as AddressInfo).port + }) + + afterEach(async () => { + await appInstance.close() + }) + + test('returns invalid_mime_type for content-type headers with tabs on the wire', async () => { + const response = await sendRawRequest( + port, + [ + 'POST /bucket HTTP/1.1', + 'Host: 127.0.0.1', + 'Connection: close', + `Authorization: Bearer ${await serviceKeyAsync}`, + 'Content-Type: image/\tpng', + 'Content-Length: 0', + '', + '', + ].join('\r\n') + ) + + expect(response.statusLine).toBe('HTTP/1.1 400 Bad Request') + expect(JSON.parse(response.body)).toEqual({ + statusCode: '415', + error: 'invalid_mime_type', + message: 'Invalid Content-Type header', + }) + }) +}) diff --git a/src/test/bucket.test.ts b/src/test/bucket.test.ts index 89d8e7be7..76e74b436 100644 --- a/src/test/bucket.test.ts +++ b/src/test/bucket.test.ts @@ -1,23 +1,27 @@ -'use strict' -import dotenv from 'dotenv' -import app from '../app' -import { S3Backend } from '../storage/backend' -import { FastifyInstance } from 'fastify' import { getPostgresConnection, getServiceKeyUser } from '@internal/database' import { StorageKnexDB } from '@storage/database' +import { randomUUID } from 'crypto' +import dotenv from 'dotenv' +import { FastifyInstance } from 'fastify' +import app from '../app' import { getConfig } from '../config' +import { S3Backend } from '../storage/backend' dotenv.config({ path: '.env.test' }) const anonKey = process.env.ANON_KEY || '' +const authenticatedKey = process.env.AUTHENTICATED_KEY || '' +const serviceKey = process.env.SERVICE_KEY || '' +const { tenantId } = getConfig() let appInstance: FastifyInstance +let adminDb: StorageKnexDB -beforeAll(() => { - jest.spyOn(S3Backend.prototype, 'deleteObjects').mockImplementation(() => { +beforeAll(async () => { + vi.spyOn(S3Backend.prototype, 'deleteObjects').mockImplementation(() => { return Promise.resolve() }) - jest.spyOn(S3Backend.prototype, 'getObject').mockImplementation(() => { + vi.spyOn(S3Backend.prototype, 'getObject').mockImplementation(() => { return Promise.resolve({ metadata: { httpStatusCode: 200, @@ -32,10 +36,23 @@ beforeAll(() => { body: Buffer.from(''), }) }) + + const serviceKeyUser = await getServiceKeyUser(tenantId) + const pg = await getPostgresConnection({ + superUser: serviceKeyUser, + user: serviceKeyUser, + tenantId, + host: 'localhost', + }) + + adminDb = new StorageKnexDB(pg, { + host: 'localhost', + tenantId, + }) }) beforeEach(() => { - jest.clearAllMocks() + vi.clearAllMocks() appInstance = app() }) @@ -43,6 +60,51 @@ afterEach(async () => { await appInstance.close() }) +afterAll(async () => { + await adminDb.destroyConnection() +}) + +async function createBucket(name: string, authorization = authenticatedKey) { + const response = await appInstance.inject({ + method: 'POST', + url: '/bucket', + headers: { + authorization: `Bearer ${authorization}`, + }, + payload: { + name, + }, + }) + + expect(response.statusCode).toBe(200) + expect(response.json()).toEqual({ + name, + }) +} + +async function seedObjects(bucketId: string, objectNames: string[]) { + await Promise.all( + objectNames.map((name) => + adminDb.createObject({ + name, + owner: randomUUID(), + bucket_id: bucketId, + metadata: { size: 1 }, + user_metadata: null, + version: undefined, + }) + ) + ) +} + +async function cleanupBucket(bucketId: string, objectNames: string[] = []) { + if (objectNames.length > 0) { + await adminDb.deleteObjects(bucketId, objectNames, 'name') + } + + await adminDb.deleteBucket(bucketId) +} + /* * GET /bucket/:id */ @@ -143,7 +205,7 @@ describe('testing GET all buckets', () => { [{ 'x-client-info': 'supabase-py/2.18.1' }, true], [{ 'x-client-info': 'supabase-py/2.19.0' }, true], ]) { - test.only(`Should ${shouldIncludeType ? '' : 'NOT '}include type for ${JSON.stringify( + test(`Should ${shouldIncludeType ? '' : 'NOT '}include type for ${JSON.stringify( headers )} client`, async () => { const response = await appInstance.inject({ @@ -182,24 +244,37 @@ describe('testing GET all buckets', () => { }) test('user is able to get buckets with limit, offset, search and sorting', async () => { - const response = await appInstance.inject({ - method: 'GET', - url: `/bucket?limit=1&offset=3&sortColumn=name&sortOrder=asc&search=bucket`, - headers: { - authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, - }, - }) - expect(response.statusCode).toBe(200) - const responseJSON = JSON.parse(response.body) - expect(responseJSON.length).toEqual(1) - expect(responseJSON[0]).toMatchObject({ - id: 'bucket4', - name: 'bucket4', - type: expect.any(String), - public: false, - file_size_limit: null, - allowed_mime_types: null, - }) + const prefix = `list-bucket-${randomUUID()}` + const bucketIds = ['a', 'b', 'c', 'd'].map((suffix) => `${prefix}-${suffix}`) + + try { + for (const bucketId of bucketIds) { + await createBucket(bucketId) + } + + const response = await appInstance.inject({ + method: 'GET', + url: `/bucket?limit=1&offset=3&sortColumn=name&sortOrder=asc&search=${encodeURIComponent( + prefix + )}`, + headers: { + authorization: `Bearer ${authenticatedKey}`, + }, + }) + expect(response.statusCode).toBe(200) + const responseJSON = response.json() + expect(responseJSON).toHaveLength(1) + expect(responseJSON[0]).toMatchObject({ + id: bucketIds[3], + name: bucketIds[3], + type: expect.any(String), + public: false, + file_size_limit: null, + allowed_mime_types: null, + }) + } finally { + await Promise.all(bucketIds.map((bucketId) => cleanupBucket(bucketId))) + } }) test('limit=0 returns 400', async () => { @@ -230,19 +305,25 @@ describe('testing GET all buckets', () => { */ describe('testing POST bucket', () => { test('user is able to create a bucket', async () => { - const response = await appInstance.inject({ - method: 'POST', - url: `/bucket`, - headers: { - authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, - }, - payload: { - name: 'newbucket', - }, - }) - expect(response.statusCode).toBe(200) - const responseJSON = JSON.parse(response.body) - expect(responseJSON.name).toBe('newbucket') + const bucketId = `newbucket-${randomUUID()}` + + try { + const response = await appInstance.inject({ + method: 'POST', + url: `/bucket`, + headers: { + authorization: `Bearer ${authenticatedKey}`, + }, + payload: { + name: bucketId, + }, + }) + expect(response.statusCode).toBe(200) + const responseJSON = response.json() + expect(responseJSON.name).toBe(bucketId) + } finally { + await cleanupBucket(bucketId) + } }) test('user is not able to create a bucket with a /', async () => { @@ -361,10 +442,11 @@ describe('testing public bucket functionality', () => { url: `/object/public/public-bucket/favicon.ico`, }) expect(publicResponse.statusCode).toBe(200) + expect(publicResponse.headers['x-robots-tag']).toBe('none') expect(publicResponse.headers['etag']).toBe('abc') expect(publicResponse.headers['last-modified']).toBe('Thu, 12 Aug 2021 16:00:00 GMT') - const mockGetObject = jest.spyOn(S3Backend.prototype, 'getObject') + const mockGetObject = vi.spyOn(S3Backend.prototype, 'getObject') mockGetObject.mockRejectedValue({ $metadata: { httpStatusCode: 304, @@ -443,18 +525,35 @@ describe('testing public bucket functionality', () => { }) expect(response.statusCode).toBe(400) }) + + test('user is not able to update a bucket with empty payload', async () => { + const bucketId = 'public-bucket' + const response = await appInstance.inject({ + method: 'PUT', + url: `/bucket/${bucketId}`, + headers: { + authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, + }, + payload: {}, + }) + expect(response.statusCode).toBe(400) + }) }) describe('testing count objects in bucket', () => { const { tenantId } = getConfig() + const testObjectCount = 27 + const testOwnerId = randomUUID() let db: StorageKnexDB + let testBucketId: string + let testObjectNames: string[] beforeAll(async () => { - const superUser = await getServiceKeyUser(tenantId) + const serviceKeyUser = await getServiceKeyUser(tenantId) const pg = await getPostgresConnection({ - superUser, - user: superUser, - tenantId: tenantId, + superUser: serviceKeyUser, + user: serviceKeyUser, + tenantId, host: 'localhost', }) @@ -462,16 +561,50 @@ describe('testing count objects in bucket', () => { host: 'localhost', tenantId, }) + + testBucketId = `count-objects-${randomUUID()}` + testObjectNames = Array.from({ length: testObjectCount }, (_, idx) => { + return `fixtures/count-object-${idx}` + }) + + await db.createBucket({ + id: testBucketId, + name: testBucketId, + public: false, + owner: testOwnerId, + file_size_limit: null, + allowed_mime_types: null, + type: 'STANDARD', + }) + + await Promise.all( + testObjectNames.map((name) => { + return db.createObject({ + name, + owner: testOwnerId, + bucket_id: testBucketId, + metadata: { size: 1 }, + user_metadata: null, + version: undefined, + }) + }) + ) + }) + + afterAll(async () => { + await db.deleteObjects(testBucketId, testObjectNames, 'name') + await db.deleteBucket(testBucketId) + await db.destroyConnection() }) it('should return correct object count', async () => { - await expect(db.countObjectsInBucket('bucket2')).resolves.toBe(27) + await expect(db.countObjectsInBucket(testBucketId)).resolves.toBe(testObjectCount) }) it('should return limited object count', async () => { - await expect(db.countObjectsInBucket('bucket2', 22)).resolves.toBe(22) + await expect(db.countObjectsInBucket(testBucketId, 22)).resolves.toBe(22) }) it('should return full object count if limit is greater than total', async () => { - await expect(db.countObjectsInBucket('bucket2', 999)).resolves.toBe(27) + await expect(db.countObjectsInBucket(testBucketId, 999)).resolves.toBe(testObjectCount) }) it('should return 0 object count if there are no objects with provided bucket id', async () => { await expect(db.countObjectsInBucket('this-is-not-a-bucket-at-all', 999)).resolves.toBe(0) @@ -480,17 +613,28 @@ describe('testing count objects in bucket', () => { describe('testing DELETE bucket', () => { test('user is able to delete a bucket', async () => { - const bucketId = 'bucket4' - const response = await appInstance.inject({ - method: 'DELETE', - url: `/bucket/${bucketId}`, - headers: { - authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, - }, - }) - expect(response.statusCode).toBe(200) - const responseJSON = JSON.parse(response.body) - expect(responseJSON.message).toBe('Successfully deleted') + const bucketId = `delete-bucket-${randomUUID()}` + let deleted = false + + try { + await createBucket(bucketId) + + const response = await appInstance.inject({ + method: 'DELETE', + url: `/bucket/${bucketId}`, + headers: { + authorization: `Bearer ${authenticatedKey}`, + }, + }) + expect(response.statusCode).toBe(200) + const responseJSON = response.json() + expect(responseJSON.message).toBe('Successfully deleted') + deleted = true + } finally { + if (!deleted) { + await cleanupBucket(bucketId) + } + } }) test('checking RLS: anon user is not able to delete a bucket', async () => { @@ -515,7 +659,28 @@ describe('testing DELETE bucket', () => { }) test('user is not able to delete bucket a non empty bucket', async () => { - const bucketId = 'bucket2' + const bucketId = `delete-non-empty-${randomUUID()}` + const objectNames = [`fixtures/${randomUUID()}`] + + try { + await createBucket(bucketId) + await seedObjects(bucketId, objectNames) + + const response = await appInstance.inject({ + method: 'DELETE', + url: `/bucket/${bucketId}`, + headers: { + authorization: `Bearer ${authenticatedKey}`, + }, + }) + expect(response.statusCode).toBe(400) + } finally { + await cleanupBucket(bucketId, objectNames) + } + }) + + test('user is not able to delete a non-existent bucket', async () => { + const bucketId = 'notfound' const response = await appInstance.inject({ method: 'DELETE', url: `/bucket/${bucketId}`, @@ -526,102 +691,263 @@ describe('testing DELETE bucket', () => { expect(response.statusCode).toBe(400) }) - test('user is not able to delete a non-existent bucket', async () => { - const bucketId = 'notfound' + test('user is not able to delete a non-existent bucket with an empty json body', async () => { + const bucketId = `delete-empty-json-${randomUUID()}` + const response = await appInstance.inject({ method: 'DELETE', url: `/bucket/${bucketId}`, headers: { authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, + 'content-type': 'application/json', }, + payload: '', }) + expect(response.statusCode).toBe(400) + expect(response.json()).toEqual({ + statusCode: '404', + error: 'Bucket not found', + message: 'Bucket not found', + }) + }) + + test('user is able to delete a bucket with an empty json body', async () => { + const bucketId = `delete-empty-json-success-${randomUUID()}` + let created = false + let deleted = false + + try { + const createResponse = await appInstance.inject({ + method: 'POST', + url: '/bucket', + headers: { + authorization: `Bearer ${serviceKey}`, + }, + payload: { + name: bucketId, + }, + }) + + expect(createResponse.statusCode).toBe(200) + expect(createResponse.json()).toEqual({ + name: bucketId, + }) + created = true + + const response = await appInstance.inject({ + method: 'DELETE', + url: `/bucket/${bucketId}`, + headers: { + authorization: `Bearer ${serviceKey}`, + 'content-type': 'application/json', + }, + payload: '', + }) + + expect(response.statusCode).toBe(200) + deleted = true + expect(response.json()).toEqual({ + message: 'Successfully deleted', + }) + } finally { + if (created && !deleted) { + await appInstance.inject({ + method: 'DELETE', + url: `/bucket/${bucketId}`, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + } + } }) }) describe('testing EMPTY bucket', () => { - test('user is able to empty a bucket', async () => { - const bucketId = 'bucket3' + test('user is not able to empty a non existent bucket with an empty json body', async () => { + const bucketId = `empty-empty-json-${randomUUID()}` + const response = await appInstance.inject({ method: 'POST', url: `/bucket/${bucketId}/empty`, headers: { authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, + 'content-type': 'application/json', }, + payload: '', + }) + + expect(response.statusCode).toBe(400) + expect(response.json()).toEqual({ + statusCode: '404', + error: 'Bucket not found', + message: 'Bucket not found', }) - expect(response.statusCode).toBe(200) - const responseJSON = JSON.parse(response.body) - expect(responseJSON.message).toBe( - 'Empty bucket has been queued. Completion may take up to an hour.' - ) + }) + + test('user is able to empty a bucket with an empty json body', async () => { + const bucketId = `empty-empty-json-success-${randomUUID()}` + let created = false + + try { + const createResponse = await appInstance.inject({ + method: 'POST', + url: '/bucket', + headers: { + authorization: `Bearer ${serviceKey}`, + }, + payload: { + name: bucketId, + }, + }) + + expect(createResponse.statusCode).toBe(200) + expect(createResponse.json()).toEqual({ + name: bucketId, + }) + created = true + + const response = await appInstance.inject({ + method: 'POST', + url: `/bucket/${bucketId}/empty`, + headers: { + authorization: `Bearer ${serviceKey}`, + 'content-type': 'application/json', + }, + payload: '', + }) + + expect(response.statusCode).toBe(200) + expect(response.json()).toEqual({ + message: 'Empty bucket has been queued. Completion may take up to an hour.', + }) + } finally { + if (created) { + await appInstance.inject({ + method: 'DELETE', + url: `/bucket/${bucketId}`, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + } + } + }) + + test('user is able to empty a bucket', async () => { + const bucketId = `empty-bucket-${randomUUID()}` + const objectNames = [`fixtures/${randomUUID()}`] + + try { + await createBucket(bucketId) + await seedObjects(bucketId, objectNames) + + const response = await appInstance.inject({ + method: 'POST', + url: `/bucket/${bucketId}/empty`, + headers: { + authorization: `Bearer ${authenticatedKey}`, + }, + }) + expect(response.statusCode).toBe(200) + const responseJSON = response.json() + expect(responseJSON.message).toBe( + 'Empty bucket has been queued. Completion may take up to an hour.' + ) + } finally { + await cleanupBucket(bucketId, objectNames) + } }) test('user is able to empty a bucket with a service key', async () => { - const bucketId = 'bucket3a' + const bucketId = `empty-bucket-service-${randomUUID()}` + const objectNames = [`service-empty-a-${randomUUID()}`, `service-empty-b-${randomUUID()}`] - // confirm there are items in the bucket before empty - const responseList = await appInstance.inject({ - method: 'POST', - url: '/object/list/' + bucketId, - headers: { - authorization: `Bearer ${process.env.SERVICE_KEY}`, - }, - payload: { - prefix: '', - limit: 10, - offset: 0, - }, - }) - expect(responseList.statusCode).toBe(200) - expect(responseList.json()).toHaveLength(2) + try { + await createBucket(bucketId, serviceKey) + await seedObjects(bucketId, objectNames) - const response = await appInstance.inject({ - method: 'POST', - url: `/bucket/${bucketId}/empty`, - headers: { - authorization: `Bearer ${process.env.SERVICE_KEY}`, - }, - }) - expect(response.statusCode).toBe(200) - const responseJSON = JSON.parse(response.body) - expect(responseJSON.message).toBe( - 'Empty bucket has been queued. Completion may take up to an hour.' - ) + // confirm there are items in the bucket before empty + const responseList = await appInstance.inject({ + method: 'POST', + url: '/object/list/' + bucketId, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + payload: { + prefix: '', + limit: 10, + offset: 0, + }, + }) + expect(responseList.statusCode).toBe(200) + expect(responseList.json()).toHaveLength(2) - // confirm the bucket is actually empty after - const responseList2 = await appInstance.inject({ - method: 'POST', - url: '/object/list/' + bucketId, - headers: { - authorization: `Bearer ${process.env.SERVICE_KEY}`, - }, - payload: { - prefix: '', - }, - }) - expect(responseList2.statusCode).toBe(200) - expect(responseList2.json()).toHaveLength(0) + const response = await appInstance.inject({ + method: 'POST', + url: `/bucket/${bucketId}/empty`, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + expect(response.statusCode).toBe(200) + const responseJSON = response.json() + expect(responseJSON.message).toBe( + 'Empty bucket has been queued. Completion may take up to an hour.' + ) + + // confirm the bucket is actually empty after + const responseList2 = await appInstance.inject({ + method: 'POST', + url: '/object/list/' + bucketId, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + payload: { + prefix: '', + }, + }) + expect(responseList2.statusCode).toBe(200) + expect(responseList2.json()).toHaveLength(0) + } finally { + await cleanupBucket(bucketId, objectNames) + } }) - test('user is able to delete a bucket', async () => { - const bucketId = 'bucket3' - const response = await appInstance.inject({ - method: 'POST', - url: `/bucket/${bucketId}/empty`, - headers: { - authorization: `Bearer ${anonKey}`, - }, - }) - expect(response.statusCode).toBe(400) + test('anon user is not able to empty a bucket', async () => { + const bucketId = `empty-bucket-anon-${randomUUID()}` + + try { + await createBucket(bucketId) + + const response = await appInstance.inject({ + method: 'POST', + url: `/bucket/${bucketId}/empty`, + headers: { + authorization: `Bearer ${anonKey}`, + }, + }) + expect(response.statusCode).toBe(400) + } finally { + await cleanupBucket(bucketId) + } }) test('user is not able to empty a bucket without Auth Header', async () => { - const bucketId = 'bucket3' - const response = await appInstance.inject({ - method: 'POST', - url: `/bucket/${bucketId}/empty`, - }) - expect(response.statusCode).toBe(400) + const bucketId = `empty-bucket-no-auth-${randomUUID()}` + + try { + await createBucket(bucketId) + + const response = await appInstance.inject({ + method: 'POST', + url: `/bucket/${bucketId}/empty`, + }) + expect(response.statusCode).toBe(400) + } finally { + await cleanupBucket(bucketId) + } }) test('user is not able to empty a non existent bucket', async () => { @@ -637,14 +963,21 @@ describe('testing EMPTY bucket', () => { }) test('user is able to empty an already empty bucket', async () => { - const bucketId = 'bucket5' - const response = await appInstance.inject({ - method: 'POST', - url: `/bucket/${bucketId}/empty`, - headers: { - authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, - }, - }) - expect(response.statusCode).toBe(200) + const bucketId = `empty-bucket-already-empty-${randomUUID()}` + + try { + await createBucket(bucketId) + + const response = await appInstance.inject({ + method: 'POST', + url: `/bucket/${bucketId}/empty`, + headers: { + authorization: `Bearer ${authenticatedKey}`, + }, + }) + expect(response.statusCode).toBe(200) + } finally { + await cleanupBucket(bucketId) + } }) }) diff --git a/src/test/cdn.test.ts b/src/test/cdn.test.ts index 0bd260d17..2478b1336 100644 --- a/src/test/cdn.test.ts +++ b/src/test/cdn.test.ts @@ -6,38 +6,42 @@ mergeConfig({ cdnPurgeEndpointKey: 'test-key', }) -import app from '../app' - -jest.mock('axios', () => { +vi.mock('axios', () => { const instance = { - post: jest.fn(), + post: vi.fn(), interceptors: { request: { - use: jest.fn(), + use: vi.fn(), }, response: { - use: jest.fn(), + use: vi.fn(), }, }, } - return { - create: jest.fn().mockReturnValue(instance), + const axiosMock = { + create: vi.fn().mockReturnValue(instance), ...instance, } + + return { + default: axiosMock, + ...axiosMock, + } }) -import { useStorage } from './utils/storage' import axios from 'axios' -import { Readable } from 'stream' -import { SignJWT } from 'jose' import { FastifyInstance } from 'fastify' +import { SignJWT } from 'jose' +import { Readable } from 'stream' +import { useStorage } from './utils/storage' const { serviceKeyAsync, anonKeyAsync, tenantId, jwtSecret } = getConfig() describe('CDN Cache Manager', () => { const storageHook = useStorage() let appInstance: FastifyInstance + let buildApp: typeof import('../app').default const bucketName = 'cdn-cache-manager-test-' + Date.now() beforeAll(async () => { @@ -45,15 +49,16 @@ describe('CDN Cache Manager', () => { id: bucketName, name: bucketName, }) + buildApp = (await import('../app')).default }) beforeEach(() => { - appInstance = app() + appInstance = buildApp() }) afterEach(async () => { await appInstance.close() - jest.clearAllMocks() + vi.clearAllMocks() }) afterAll(() => { @@ -94,16 +99,16 @@ describe('CDN Cache Manager', () => { await storageHook.storage.from(bucketName).uploadNewObject({ isUpsert: true, objectName, + userMetadata: {}, file: { body: Readable.from(Buffer.from('test')), cacheControl: 'public, max-age=31536000', mimeType: 'text/plain', isTruncated: () => false, - userMetadata: {}, }, }) - const spy = jest + const spy = vi .spyOn(axios, 'post') .mockReturnValue(Promise.resolve({ data: { message: 'success' } })) @@ -119,12 +124,12 @@ describe('CDN Cache Manager', () => { const body = await response.json() expect(body).toEqual({ message: 'success' }) - expect(spy).toBeCalledWith('/purge', { + expect(spy).toHaveBeenCalledWith('/purge', { tenant: { ref: tenantId, }, bucketId: bucketName, - objectName: objectName, + objectName, }) }) }) diff --git a/src/test/common.ts b/src/test/common.ts index 704891c03..675c9d99e 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -1,32 +1,67 @@ import { HeadBucketCommand, S3Client } from '@aws-sdk/client-s3' -import app from '../admin-app' -import { S3Backend } from '../storage/backend' -import { Queue } from '@internal/queue' import { isS3Error } from '@internal/errors' +import { Queue } from '@internal/queue' +import type { FastifyInstance } from 'fastify' import path from 'path' +import { S3Backend } from '../storage/backend' -export const adminApp = app({}) - -const ENV = process.env +const ENV = { ...process.env } const projectRoot = path.join(__dirname, '..', '..') +let sharedAdminAppPromise: Promise | undefined + +export async function createAdminApp(): Promise { + const { default: app } = await import('../admin-app') + return app({}) +} + +async function getSharedAdminApp() { + if (!sharedAdminAppPromise) { + const appPromise = createAdminApp() + sharedAdminAppPromise = appPromise.catch((error) => { + if (sharedAdminAppPromise === appPromise) { + sharedAdminAppPromise = undefined + } + + throw error + }) + } + + return sharedAdminAppPromise +} + +type SharedAdminApp = Pick + +export const adminApp: SharedAdminApp = { + inject: ((...args: Parameters) => + getSharedAdminApp().then((app) => app.inject(...args))) as FastifyInstance['inject'], + close: (async (...args: Parameters) => { + if (!sharedAdminAppPromise) { + return + } + + const appPromise = sharedAdminAppPromise + sharedAdminAppPromise = undefined + const app = await appPromise + + return app.close(...args) + }) as FastifyInstance['close'], +} + export function useMockQueue() { - const queueSpy: jest.SpyInstance | undefined = undefined beforeEach(() => { mockQueue() }) - - return queueSpy } export function mockQueue() { - const sendSpy = jest.fn() - const insertSpy = jest.fn() - const queueSpy = jest.fn().mockReturnValue({ + const sendSpy = vi.fn() + const insertSpy = vi.fn() + const queueSpy = vi.fn().mockReturnValue({ send: sendSpy, insert: insertSpy, }) - jest.spyOn(Queue, 'getInstance').mockImplementation(queueSpy) + vi.spyOn(Queue, 'getInstance').mockImplementation(queueSpy) return { queueSpy, sendSpy, insertSpy } } @@ -35,8 +70,8 @@ export function useMockObject() { beforeEach(() => { process.env = { ...ENV } - jest.clearAllMocks() - jest.spyOn(S3Backend.prototype, 'getObject').mockResolvedValue({ + vi.clearAllMocks() + vi.spyOn(S3Backend.prototype, 'getObject').mockResolvedValue({ metadata: { httpStatusCode: 200, size: 3746, @@ -50,7 +85,7 @@ export function useMockObject() { body: Buffer.from(''), }) - jest.spyOn(S3Backend.prototype, 'uploadObject').mockResolvedValue({ + vi.spyOn(S3Backend.prototype, 'uploadObject').mockResolvedValue({ httpStatusCode: 200, size: 3746, mimetype: 'image/png', @@ -60,17 +95,17 @@ export function useMockObject() { contentLength: 3746, }) - jest.spyOn(S3Backend.prototype, 'copyObject').mockResolvedValue({ + vi.spyOn(S3Backend.prototype, 'copyObject').mockResolvedValue({ httpStatusCode: 200, lastModified: new Date('Thu, 12 Aug 2021 16:00:00 GMT'), eTag: 'abc', }) - jest.spyOn(S3Backend.prototype, 'deleteObject').mockResolvedValue() + vi.spyOn(S3Backend.prototype, 'deleteObject').mockResolvedValue() - jest.spyOn(S3Backend.prototype, 'deleteObjects').mockResolvedValue() + vi.spyOn(S3Backend.prototype, 'deleteObjects').mockResolvedValue() - jest.spyOn(S3Backend.prototype, 'headObject').mockResolvedValue({ + vi.spyOn(S3Backend.prototype, 'headObject').mockResolvedValue({ httpStatusCode: 200, size: 3746, mimetype: 'image/png', @@ -80,13 +115,13 @@ export function useMockObject() { contentLength: 3746, }) - jest - .spyOn(S3Backend.prototype, 'privateAssetUrl') - .mockResolvedValue(`local:///${projectRoot}/data/sadcat.jpg`) + vi.spyOn(S3Backend.prototype, 'privateAssetUrl').mockResolvedValue( + `local:///${projectRoot}/data/sadcat.jpg` + ) }) afterEach(() => { - jest.clearAllMocks() + vi.clearAllMocks() }) } diff --git a/src/test/database-protection.test.ts b/src/test/database-protection.test.ts new file mode 100644 index 000000000..137516013 --- /dev/null +++ b/src/test/database-protection.test.ts @@ -0,0 +1,132 @@ +import { DatabaseError } from 'pg' +import { useStorage, withDeleteEnabled } from './utils/storage' + +describe('Database Protection Triggers', () => { + const tHelper = useStorage() + const testBucketName = `test-db-protection-${Date.now()}` + + beforeAll(async () => { + await tHelper.database.createBucket({ + id: testBucketName, + name: testBucketName, + }) + }) + + afterAll(async () => { + await tHelper.database.connection.dispose() + }) + + describe('Direct DELETE protection (migration 0050)', () => { + it('should prevent direct DELETE on storage.buckets without storage.allow_delete_query', async () => { + const db = tHelper.database.connection.pool.acquire() + const testBucket = `temp-bucket-${Date.now()}` + + // Create a test bucket + await db.raw('INSERT INTO storage.buckets (id, name) VALUES (?, ?)', [testBucket, testBucket]) + + // Attempt to delete without setting storage.allow_delete_query + try { + await db.raw('DELETE FROM storage.buckets WHERE id = ?', [testBucket]) + throw new Error('Expected DELETE to be blocked by trigger') + } catch (error) { + const dbError = error as DatabaseError + expect(dbError.code).toBe('42501') // PostgreSQL error code for insufficient privilege + expect(dbError.message).toContain('Direct deletion from storage tables is not allowed') + } + + // Verify bucket still exists + const result = await db.raw('SELECT id FROM storage.buckets WHERE id = ?', [testBucket]) + expect(result.rows).toHaveLength(1) + + // Cleanup: delete with proper config + await withDeleteEnabled(db, async (db) => { + await db.raw('DELETE FROM storage.buckets WHERE id = ?', [testBucket]) + }) + }) + + it('should prevent direct DELETE on storage.objects without storage.allow_delete_query', async () => { + const db = tHelper.database.connection.pool.acquire() + const testObjectName = `test-object-${Date.now()}.txt` + + // Create a test object + await db.raw( + 'INSERT INTO storage.objects (bucket_id, name, owner, version) VALUES (?, ?, ?, ?)', + [testBucketName, testObjectName, null, '1'] + ) + + // Attempt to delete without setting storage.allow_delete_query + try { + await db.raw('DELETE FROM storage.objects WHERE bucket_id = ? AND name = ?', [ + testBucketName, + testObjectName, + ]) + throw new Error('Expected DELETE to be blocked by trigger') + } catch (error) { + const dbError = error as DatabaseError + expect(dbError.code).toBe('42501') + expect(dbError.message).toContain('Direct deletion from storage tables is not allowed') + } + + // Verify object still exists + const result = await db.raw( + 'SELECT name FROM storage.objects WHERE bucket_id = ? AND name = ?', + [testBucketName, testObjectName] + ) + expect(result.rows).toHaveLength(1) + + // Cleanup: delete with proper config + await withDeleteEnabled(db, async (db) => { + await db.raw('DELETE FROM storage.objects WHERE bucket_id = ? AND name = ?', [ + testBucketName, + testObjectName, + ]) + }) + }) + + it('should allow DELETE on storage.buckets when storage.allow_delete_query is set', async () => { + const db = tHelper.database.connection.pool.acquire() + const testBucket = `temp-bucket-allow-${Date.now()}` + + await withDeleteEnabled(db, async (db) => { + // Create a test bucket + await db.raw('INSERT INTO storage.buckets (id, name) VALUES (?, ?)', [ + testBucket, + testBucket, + ]) + + // Delete with proper config should succeed + await db.raw('DELETE FROM storage.buckets WHERE id = ?', [testBucket]) + + // Verify bucket is deleted + const result = await db.raw('SELECT id FROM storage.buckets WHERE id = ?', [testBucket]) + expect(result.rows).toHaveLength(0) + }) + }) + + it('should allow DELETE on storage.objects when storage.allow_delete_query is set', async () => { + const db = tHelper.database.connection.pool.acquire() + const testObjectName = `test-object-allow-${Date.now()}.txt` + + await withDeleteEnabled(db, async (db) => { + // Create a test object + await db.raw( + 'INSERT INTO storage.objects (bucket_id, name, owner, version) VALUES (?, ?, ?, ?)', + [testBucketName, testObjectName, null, '1'] + ) + + // Delete with proper config should succeed + await db.raw('DELETE FROM storage.objects WHERE bucket_id = ? AND name = ?', [ + testBucketName, + testObjectName, + ]) + + // Verify object is deleted + const result = await db.raw( + 'SELECT name FROM storage.objects WHERE bucket_id = ? AND name = ?', + [testBucketName, testObjectName] + ) + expect(result.rows).toHaveLength(0) + }) + }) + }) +}) diff --git a/src/test/db/02-dummy-data.sql b/src/test/db/02-dummy-data.sql index 475ebb531..2788d9874 100644 --- a/src/test/db/02-dummy-data.sql +++ b/src/test/db/02-dummy-data.sql @@ -44,6 +44,7 @@ INSERT INTO "storage"."objects" ("id", "bucket_id", "name", "owner", "created_at ('24f70210-62aa-4daa-9909-693b3febd8fd', 'bucket2', 'authenticated/move-orig-4.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-22 22:29:15.14732+00', '2021-02-22 22:29:15.14732+00', '2021-03-02 09:32:17.116+00', '{"mimetype": "image/png", "size": 1234}', NULL), ('18dc5e3b-4fb1-45a7-bfa4-d99b0784be31', 'bucket2', 'authenticated/move-orig-5.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-02-22 22:29:15.14732+00', '2021-02-22 22:29:15.14732+00', '2021-03-02 09:32:17.116+00', '{"mimetype": "image/png", "size": 1234}', NULL), ('8377527d-3518-4dc8-8290-c6926470e795', 'bucket2', 'folder/subfolder/public-all-permissions.png', 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', '2021-02-17 10:26:42.791214+00', '2021-02-17 11:03:30.025116+00', '2021-02-17 10:26:42.791214+00', '{"size": 1234}', NULL), +('8377527d-3518-4dc8-8290-c6926470e796', 'bucket2', 'folder/UPPER-folder/public-all-permissions.png', 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', '2021-02-17 10:26:42.791214+00', '2021-02-17 11:03:30.025116+00', '2021-02-17 10:26:42.791214+00', '{"size": 1234}', NULL), ('b39ae4ab-802b-4c42-9271-3f908c34363c', 'bucket2', 'private/sadcat-upload3.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-03-01 08:53:29.567975+00', '2021-03-01 08:53:29.567975+00', '2021-03-01 08:53:29.567975+00', '{"mimetype": "image/svg+xml", "size": 1234}', NULL), ('8098E1AC-C744-4368-86DF-71B60CCDE221', 'bucket3', 'sadcat-upload3.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-03-01 08:53:29.567975+00', '2021-03-01 08:53:29.567975+00', '2021-03-01 08:53:29.567975+00', '{"mimetype": "image/svg+xml", "size": 1234}', NULL), ('D3EB488E-94F4-46CD-86D3-242C13B95BAC', 'bucket3', 'sadcat-upload2.png', '317eadce-631a-4429-a0bb-f19a7a517b4a', '2021-03-01 08:53:29.567975+00', '2021-03-01 08:53:29.567975+00', '2021-03-01 08:53:29.567975+00', '{"mimetype": "image/svg+xml", "size": 1234}', NULL), diff --git a/src/test/db/import-dummy-data.ts b/src/test/db/import-dummy-data.ts index 44e16bf9b..6cd518f99 100644 --- a/src/test/db/import-dummy-data.ts +++ b/src/test/db/import-dummy-data.ts @@ -4,7 +4,7 @@ import { Client } from 'pg' const migrations = ['01-auth-schema.sql', '02-dummy-data.sql'] -;(async () => { +void (async () => { const dbConfig = { connectionString: process.env.DATABASE_URL, } diff --git a/src/test/iceberg.test.ts b/src/test/iceberg.test.ts index 5239a7860..6ff8b3651 100644 --- a/src/test/iceberg.test.ts +++ b/src/test/iceberg.test.ts @@ -1,16 +1,16 @@ -import { createBucketIfNotExists, useStorage } from './utils/storage' -import makeApp from '../app' -import { getConfig } from '../config' +import assert from 'node:assert' +import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3' +import { isS3Error } from '@internal/errors' import { CreateTableResponse, LoadTableResult, RestCatalogClient, } from '@storage/protocols/iceberg/catalog' -import { FastifyInstance } from 'fastify' import { KnexMetastore, Metastore } from '@storage/protocols/iceberg/knex' -import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3' -import assert from 'node:assert' -import { isS3Error } from '@internal/errors' +import { FastifyInstance } from 'fastify' +import makeApp from '../app' +import { getConfig, mergeConfig } from '../config' +import { createBucketIfNotExists, useStorage } from './utils/storage' const { serviceKeyAsync, @@ -31,10 +31,15 @@ describe('Iceberg Catalog', () => { multiTenant: false, schema: 'storage', }) + + mergeConfig({ + icebergMaxCatalogsCount: 1e8, + icebergMaxNamespaceCount: 1e8, + }) }) afterEach(async () => { - jest.restoreAllMocks() + vi.restoreAllMocks() }) afterAll(async () => { @@ -42,6 +47,64 @@ describe('Iceberg Catalog', () => { await t.database.connection.pool.destroy() }) + it('can create an analytic bucket', async () => { + const bucketName = t.random.name('ice-bucket') + + const response = await app.inject({ + method: 'POST', + url: '/iceberg/bucket', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${await serviceKeyAsync}`, + }, + payload: { + name: bucketName, + }, + }) + + const resp = await response.json() + expect(response.statusCode).toBe(200) + expect(resp.id).toBe(bucketName) + }) + + it('can list analytic buckets', async () => { + const bucketName = t.random.name('ice-bucket') + await t.storage.createIcebergBucket({ + name: bucketName, + }) + + const response = await app.inject({ + method: 'GET', + url: '/iceberg/bucket', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${await serviceKeyAsync}`, + }, + }) + + const resp = await response.json() + expect(response.statusCode).toBe(200) + expect(resp.length).toBeGreaterThan(0) + }) + + it('can delete analytic bucket', async () => { + const bucketName = t.random.name('ice-bucket') + await t.storage.createIcebergBucket({ + name: bucketName, + }) + + const response = await app.inject({ + method: 'DELETE', + url: `/iceberg/bucket/${bucketName}`, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${await serviceKeyAsync}`, + }, + }) + + expect(response.statusCode).toBe(200) + }) + it('can create a table bucket', async () => { const bucketName = t.random.name('ice-bucket') @@ -67,19 +130,16 @@ describe('Iceberg Catalog', () => { const bucketName = t.random.name('ice-bucket') await t.storage.createIcebergBucket({ name: bucketName, - id: bucketName, }) - jest.spyOn(RestCatalogClient.prototype, 'getConfig').mockResolvedValue( - Promise.resolve({ - defaults: { - prefix: bucketName, - }, - overrides: { - prefix: bucketName, - }, - }) - ) + vi.spyOn(RestCatalogClient.prototype, 'getConfig').mockResolvedValue({ + defaults: { + prefix: bucketName, + }, + overrides: { + prefix: bucketName, + }, + }) const response = await app.inject({ method: 'GET', @@ -93,7 +153,12 @@ describe('Iceberg Catalog', () => { const resp = await response.json() expect(response.statusCode).toBe(200) expect(resp.defaults).toEqual({ + 'io-impl': 'org.apache.iceberg.aws.s3.S3FileIO', prefix: bucketName, + 'rest-metrics-reporting-enabled': 'false', + 's3.delete-enabled': 'false', + 'write.object-storage.enabled': 'true', + 'write.object-storage.partitioned-paths': 'false', }) expect(resp.overrides).toEqual({ prefix: bucketName, @@ -105,19 +170,16 @@ describe('Iceberg Catalog', () => { const bucketName = t.random.name('ice-bucket') await t.storage.createIcebergBucket({ name: bucketName, - id: bucketName, }) const namespaceName = t.random.name('namespace') - jest.spyOn(RestCatalogClient.prototype, 'createNamespace').mockResolvedValue( - Promise.resolve({ - namespace: [namespaceName], - properties: { - test: 'hello', - }, - }) - ) + vi.spyOn(RestCatalogClient.prototype, 'createNamespace').mockResolvedValue({ + namespace: [namespaceName], + properties: { + test: 'hello', + }, + }) const response = await app.inject({ method: 'POST', @@ -144,27 +206,54 @@ describe('Iceberg Catalog', () => { }) }) + it('returns InvalidParameter for invalid namespace names', async () => { + const bucketName = t.random.name('ice-bucket') + await t.storage.createIcebergBucket({ + name: bucketName, + }) + + const response = await app.inject({ + method: 'POST', + url: `/iceberg/v1/${bucketName}/namespaces`, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${await serviceKeyAsync}`, + }, + payload: { + namespace: 'awsnamespace', + }, + }) + + expect(response.statusCode).toBe(400) + expect(await response.json()).toEqual({ + error: { + code: 400, + message: 'Resource name must not start with the reserved prefix "aws"', + type: 'InvalidParameter', + }, + }) + }) + it('can list namespaces', async () => { const bucketName = t.random.name('ice-bucket') const bucket = await t.storage.createIcebergBucket({ name: bucketName, - id: bucketName, }) const namespaceName = t.random.name('namespace') - const namespace = await icebergMetastore.assignNamespace({ + const namespace = await icebergMetastore.createNamespace({ name: namespaceName, + bucketName: bucket.name, bucketId: bucket.id, tenantId: '', + metadata: {}, }) - jest.spyOn(RestCatalogClient.prototype, 'listNamespaces').mockResolvedValue( - Promise.resolve({ - namespaces: [[namespaceName]], - }) - ) + vi.spyOn(RestCatalogClient.prototype, 'listNamespaces').mockResolvedValue({ + namespaces: [[namespaceName]], + }) const response = await app.inject({ method: 'GET', @@ -187,20 +276,22 @@ describe('Iceberg Catalog', () => { const bucket = await t.storage.createIcebergBucket({ name: bucketName, - id: bucketName, }) const namespaceName = t.random.name('namespace') - const namespace = await icebergMetastore.assignNamespace({ + const namespace = await icebergMetastore.createNamespace({ name: namespaceName, + bucketName: bucket.name, bucketId: bucket.id, + tenantId: '', + metadata: {}, }) const initialNamespaces = await icebergMetastore.listNamespaces({ - bucketId: bucket.id, + catalogId: bucket.id, }) - jest.spyOn(RestCatalogClient.prototype, 'dropNamespace').mockResolvedValue(Promise.resolve()) + vi.spyOn(RestCatalogClient.prototype, 'dropNamespace').mockResolvedValue(undefined) const response = await app.inject({ method: 'DELETE', @@ -217,7 +308,7 @@ describe('Iceberg Catalog', () => { const afterDropNamespaces = await icebergMetastore.listNamespaces({ tenantId: '', - bucketId: bucket.id, + catalogId: bucket.id, }) expect(afterDropNamespaces.length).toEqual(initialNamespaces.length - 1) @@ -228,23 +319,25 @@ describe('Iceberg Catalog', () => { const bucket = await t.storage.createIcebergBucket({ name: bucketName, - id: bucketName, }) const namespaceName = t.random.name('namespace') - const namespace = await icebergMetastore.assignNamespace({ + const namespace = await icebergMetastore.createNamespace({ name: namespaceName, + bucketName: bucket.name, bucketId: bucket.id, + tenantId: '', + metadata: { + test: 'hello', + }, }) - jest.spyOn(RestCatalogClient.prototype, 'loadNamespaceMetadata').mockResolvedValue( - Promise.resolve({ - namespace: [namespace.name], - properties: { - test: 'hello', - }, - }) - ) + vi.spyOn(RestCatalogClient.prototype, 'loadNamespaceMetadata').mockResolvedValue({ + namespace: [namespace.name], + properties: { + test: 'hello', + }, + }) const response = await app.inject({ method: 'GET', @@ -270,18 +363,18 @@ describe('Iceberg Catalog', () => { const bucket = await t.storage.createIcebergBucket({ name: bucketName, - id: bucketName, }) const namespaceName = t.random.name('namespace') - const namespace = await icebergMetastore.assignNamespace({ + const namespace = await icebergMetastore.createNamespace({ name: namespaceName, + bucketName: bucket.name, bucketId: bucket.id, + tenantId: '', + metadata: {}, }) - jest - .spyOn(RestCatalogClient.prototype, 'namespaceExists') - .mockResolvedValue(Promise.resolve()) + vi.spyOn(RestCatalogClient.prototype, 'namespaceExists').mockResolvedValue(undefined) const response = await app.inject({ method: 'HEAD', @@ -299,15 +392,17 @@ describe('Iceberg Catalog', () => { describe('Table', () => { it('can create a table', async () => { const bucketName = t.random.name('ice-bucket') - await t.storage.createIcebergBucket({ + const bucket = await t.storage.createIcebergBucket({ name: bucketName, - id: bucketName, }) const namespaceName = t.random.name('namespace') - const namespace = await icebergMetastore.assignNamespace({ + const namespace = await icebergMetastore.createNamespace({ name: namespaceName, - bucketId: bucketName, + bucketName: bucket.name, + bucketId: bucket.id, + tenantId: '', + metadata: {}, }) const tableName = t.random.name('table') @@ -355,9 +450,12 @@ describe('Iceberg Catalog', () => { 's3://821c6598-0032-4345-h1sokpwnexm17nfi7n1axwxmnncg1aps1b--table-s3/metadata/00000-2eed5277-661d-47b5-84c0-30ce9dbad149.metadata.json', } as const - jest - .spyOn(RestCatalogClient.prototype, 'createTable') - .mockResolvedValue(Promise.resolve(loadTable)) + vi.spyOn(RestCatalogClient.prototype, 'createTable').mockResolvedValue(loadTable) + + vi.spyOn(RestCatalogClient.prototype, 'createNamespace').mockResolvedValue({ + namespace: [namespace.name], + properties: {}, + }) const response = await app.inject({ method: 'POST', @@ -407,15 +505,17 @@ describe('Iceberg Catalog', () => { it('can list tables in a namespace', async () => { const bucketName = t.random.name('ice-bucket') - await t.storage.createIcebergBucket({ + const bucket = await t.storage.createIcebergBucket({ name: bucketName, - id: bucketName, }) const namespaceName = t.random.name('namespace') - const namespace = await icebergMetastore.assignNamespace({ + const namespace = await icebergMetastore.createNamespace({ name: namespaceName, - bucketId: bucketName, + bucketName: bucket.name, + bucketId: bucket.id, + tenantId: '', + metadata: {}, }) const tableName = t.random.name('table') @@ -423,19 +523,18 @@ describe('Iceberg Catalog', () => { name: tableName, location: `s3://${bucketName}/tables/${tableName}`, namespaceId: namespace.id, - bucketId: bucketName, + bucketName: bucket.name, + bucketId: bucket.id, }) - jest.spyOn(RestCatalogClient.prototype, 'listTables').mockResolvedValue( - Promise.resolve({ - identifiers: [ - { - namespace: [namespace.name], - name: tableName, - }, - ], - }) - ) + vi.spyOn(RestCatalogClient.prototype, 'listTables').mockResolvedValue({ + identifiers: [ + { + namespace: [namespace.name], + name: tableName, + }, + ], + }) const response = await app.inject({ method: 'GET', @@ -459,6 +558,7 @@ describe('Iceberg Catalog', () => { const newTable = await icebergMetastore.findTableByName({ name: tableName, + namespaceId: namespace.id, }) expect(newTable.name).toEqual(tableName) @@ -466,26 +566,31 @@ describe('Iceberg Catalog', () => { it('check if table exists', async () => { const bucketName = t.random.name('ice-bucket') - await t.storage.createIcebergBucket({ + const bucket = await t.storage.createIcebergBucket({ name: bucketName, - id: bucketName, }) const namespaceName = t.random.name('namespace') - const namespace = await icebergMetastore.assignNamespace({ + const namespace = await icebergMetastore.createNamespace({ name: namespaceName, - bucketId: bucketName, + bucketName: bucket.name, + bucketId: bucket.id, + tenantId: '', + metadata: {}, }) const tableName = t.random.name('table') await icebergMetastore.createTable({ name: tableName, - bucketId: bucketName, + bucketName: bucket.name, + bucketId: bucket.id, location: `s3://${bucketName}/tables/${tableName}`, namespaceId: namespace.id, + shardId: 'my-warehouse', + shardKey: 'my-warehouse', }) - jest.spyOn(RestCatalogClient.prototype, 'tableExists').mockResolvedValue(Promise.resolve()) + vi.spyOn(RestCatalogClient.prototype, 'tableExists').mockResolvedValue(undefined) const response = await app.inject({ method: 'HEAD', @@ -501,26 +606,34 @@ describe('Iceberg Catalog', () => { it('can drop a table', async () => { const bucketName = t.random.name('ice-bucket') - await t.storage.createIcebergBucket({ + const bucket = await t.storage.createIcebergBucket({ name: bucketName, - id: bucketName, }) const namespaceName = t.random.name('namespace') - const namespace = await icebergMetastore.assignNamespace({ + const namespace = await icebergMetastore.createNamespace({ name: namespaceName, - bucketId: bucketName, + bucketName: bucket.name, + bucketId: bucket.id, + tenantId: '', + metadata: {}, }) const tableName = t.random.name('table') await icebergMetastore.createTable({ name: tableName, - bucketId: bucketName, + bucketName: bucket.name, + bucketId: bucket.id, location: `s3://${bucketName}/tables/${tableName}`, namespaceId: namespace.id, + shardId: 'my-warehouse', + shardKey: 'my-warehouse', }) - jest.spyOn(RestCatalogClient.prototype, 'dropTable').mockResolvedValue(Promise.resolve()) + vi.spyOn(RestCatalogClient.prototype, 'dropTable').mockResolvedValue(undefined) + vi.spyOn(RestCatalogClient.prototype, 'listTables').mockResolvedValue({ identifiers: [] }) + + vi.spyOn(RestCatalogClient.prototype, 'dropNamespace').mockResolvedValue(undefined) const response = await app.inject({ method: 'DELETE', @@ -537,23 +650,28 @@ describe('Iceberg Catalog', () => { it('can load table metadata', async () => { const bucketName = t.random.name('ice-bucket') - await t.storage.createIcebergBucket({ + const bucket = await t.storage.createIcebergBucket({ name: bucketName, - id: bucketName, }) const namespaceName = t.random.name('namespace') - const namespace = await icebergMetastore.assignNamespace({ + const namespace = await icebergMetastore.createNamespace({ name: namespaceName, - bucketId: bucketName, + bucketName: bucket.name, + bucketId: bucket.id, + tenantId: '', + metadata: {}, }) const tableName = t.random.name('table') await icebergMetastore.createTable({ name: tableName, - bucketId: bucketName, + bucketName: bucket.name, + bucketId: bucket.id, location: `s3://${bucketName}/tables/${tableName}`, namespaceId: namespace.id, + shardId: 'my-warehouse', + shardKey: 'my-warehouse', }) const tableMetadata: LoadTableResult = { @@ -599,9 +717,7 @@ describe('Iceberg Catalog', () => { 'metadata-location': `s3://${bucketName}/tables/${tableName}/metadata/00000-2eed5277-661d-47b5-84c0-30ce9dbad149.metadata.json`, } - jest - .spyOn(RestCatalogClient.prototype, 'loadTable') - .mockResolvedValue(Promise.resolve(tableMetadata)) + vi.spyOn(RestCatalogClient.prototype, 'loadTable').mockResolvedValue(tableMetadata) const response = await app.inject({ method: 'GET', @@ -619,6 +735,7 @@ describe('Iceberg Catalog', () => { const table = await icebergMetastore.findTableByName({ name: tableName, tenantId: '', + namespaceId: namespace.id, }) expect(table.name).toEqual(tableName) @@ -683,24 +800,27 @@ describe('Iceberg Catalog', () => { await createBucketIfNotExists(internalBucketName, minioClient) - await t.storage.createIcebergBucket({ + const bucket = await t.storage.createIcebergBucket({ name: bucketName, - id: bucketName, }) const tableName = t.random.name('table') const namespaceName = t.random.name('namespace') - const namespace = await icebergMetastore.assignNamespace({ + const namespace = await icebergMetastore.createNamespace({ name: namespaceName, - bucketId: bucketName, + bucketName: bucket.name, + bucketId: bucket.id, + tenantId: '', + metadata: {}, }) await icebergMetastore.createTable({ name: tableName, + bucketName: bucket.name, + bucketId: bucket.id, location: `s3://${internalBucketName}`, namespaceId: namespace.id, - bucketId: bucketName, }) const uploadFile = new PutObjectCommand({ diff --git a/src/test/jwt.test.ts b/src/test/jwt.test.ts deleted file mode 100644 index 0369e984a..000000000 --- a/src/test/jwt.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -import * as crypto from 'crypto' -import { generateHS512JWK, signJWT, verifyJWT } from '../internal/auth' -import { JwksConfigKey } from '../config' -import { SignJWT } from 'jose' - -describe('JWT', () => { - describe('verifyJWT with JWKS', () => { - const keys: { - type?: string - options?: object - alg: string - kid?: string - // eslint-disable-next-line @typescript-eslint/no-explicit-any - publicKey: any - // eslint-disable-next-line @typescript-eslint/no-explicit-any - privateKey: any - }[] = [ - { type: 'rsa', options: { modulusLength: 2048 }, alg: 'RS256' }, - { type: 'ec', options: { namedCurve: 'P-256' }, alg: 'ES256' }, - { type: 'ed25519', options: {}, alg: 'EdDSA' }, - ].map((desc, i) => ({ - kid: i.toString(), - ...desc, - ...crypto.generateKeyPairSync( - desc.type as 'rsa' & 'ec', - (desc.options || undefined) as object - ), - })) - - const hmacPrivateKeyWithoutKid = crypto.randomBytes(256 / 8).toString('hex') - - // without kid, so the value from the secret argument will be taken - keys.push({ - alg: 'HS256', - privateKey: Buffer.from(hmacPrivateKeyWithoutKid, 'utf-8'), - publicKey: { - export: () => ({ - doesntmatter: 'wontbeused', - }), - }, - }) - - const hmacPrivateKeyWithKid = crypto.randomBytes(256 / 8).toString('hex') - - // with kid, so the value from the JWKS will be used - keys.push({ - alg: 'HS256', - kid: keys.length.toString(), - privateKey: Buffer.from(hmacPrivateKeyWithKid, 'utf-8'), - publicKey: { - export: () => ({ - kty: 'oct', - k: Buffer.from(hmacPrivateKeyWithKid, 'utf-8').toString('base64url'), - }), - }, - }) - - const jwks = { - keys: keys.map( - ({ publicKey, kid, alg }) => - ({ - ...(publicKey as unknown as crypto.KeyObject).export({ format: 'jwk' }), - kid, - alg, - } as JwksConfigKey) - ), - } - - keys.forEach(({ privateKey, alg, kid }, keyIdx) => { - const iat = Math.trunc(Date.now() / 1000) - const exp = iat + 60 - - const parts = [ - Buffer.from(JSON.stringify({ typ: 'JWT', kid, alg }), 'utf-8').toString('base64url'), - Buffer.from(JSON.stringify({ sub: 'abcdef' + keyIdx, iat, exp }), 'utf-8').toString( - 'base64url' - ), - ] - - if (!alg.startsWith('HS')) { - const sign = crypto.createSign('SHA256') - sign.write(parts.join('.')) - sign.end() - - if (alg === 'EdDSA') { - // Ed25519 signs the raw message directly - const message = Buffer.from(parts.join('.')) - parts.push(crypto.sign(null, message, privateKey).toString('base64url')) - } else if (alg === 'ES256') { - parts.push( - sign.sign(Object.assign(privateKey, { dsaEncoding: 'ieee-p1363' }), 'base64url') - ) - } else { - parts.push(sign.sign(privateKey, 'base64url')) - } - } else { - const hmacAlgo = alg.replace('HS', 'SHA') - const hmac = crypto.createHmac(hmacAlgo, privateKey) - hmac.update(parts.join('.')) - parts.push(hmac.digest('base64url')) - } - - const jwtStr = parts.join('.') - - test(`it should verify a JWT with alg=${alg}`, async () => { - const result = await verifyJWT(jwtStr, hmacPrivateKeyWithoutKid, jwks) - expect(result.sub).toEqual('abcdef' + keyIdx) - }) - }) - - test('it should try secret if no matching jwk kty/alg found in jwks', async () => { - const jwk = await generateHS512JWK() - jwk.kid = 'abc123' - const sub = 'weird-case-secret' - const secret = crypto.randomBytes(32).toString('base64url') - - const jwtStr = await new SignJWT({ sub }) - .setIssuedAt() - .setProtectedHeader({ alg: 'HS256', kid: 'def456' }) - .sign(new TextEncoder().encode(secret)) - - const result = await verifyJWT(jwtStr, secret, { keys: [jwk] }) - expect(result.sub).toEqual(sub) - }) - - test('it should use jwt secret if jwks are missing', async () => { - const jwt = await signJWT({ sub: 'things' }, hmacPrivateKeyWithoutKid, 100) - const result = await verifyJWT(jwt, hmacPrivateKeyWithoutKid) - expect(result.sub).toEqual('things') - }) - - test('it should sign and verify using our HS256 generation', async () => { - const token = await generateHS512JWK() - token.kid = 'this-is-my-kid' - const jwt = await signJWT({ sub: 'stuff' }, token, 100) - const result = await verifyJWT(jwt, 'totally-invalid-secret-not-used', { keys: [token] }) - expect(result.sub).toEqual('stuff') - }) - - test('it should reject if secret is invalid when signing', async () => { - await expect(signJWT({ sub: 'things' }, '', 100)).rejects.toThrow( - 'Zero-length key is not supported' - ) - }) - - test('it should reject if jwt is malformed', async () => { - await expect(verifyJWT('this is not a jwt', 'and this is not a secret')).rejects.toThrow( - 'Invalid Compact JWS' - ) - }) - }) -}) diff --git a/src/test/mocks/knex-mock.ts b/src/test/mocks/knex-mock.ts index d458fa9c3..69af8e36e 100644 --- a/src/test/mocks/knex-mock.ts +++ b/src/test/mocks/knex-mock.ts @@ -8,27 +8,27 @@ import { Knex } from 'knex' export function createMockKnexReturning(returnedValue: object | object[]): Knex { // Create a fake "thenable" query builder const queryBuilder = { - insert: jest.fn().mockReturnThis(), - select: jest.fn().mockReturnThis(), - where: jest.fn().mockReturnThis(), - update: jest.fn().mockReturnThis(), - del: jest.fn().mockReturnThis(), - returning: jest.fn().mockReturnThis(), - first: jest.fn().mockResolvedValue(returnedValue), + insert: vi.fn().mockReturnThis(), + select: vi.fn().mockReturnThis(), + where: vi.fn().mockReturnThis(), + update: vi.fn().mockReturnThis(), + del: vi.fn().mockReturnThis(), + returning: vi.fn().mockReturnThis(), + first: vi.fn().mockResolvedValue(returnedValue), then: (resolve: (val: object | object[]) => void) => Promise.resolve(returnedValue).then(resolve), onConflict: {}, } // These need access to the full queryBuilder to return the right `this` - queryBuilder.onConflict = jest.fn(() => ({ - ignore: jest.fn(() => queryBuilder), - merge: jest.fn(() => queryBuilder), + queryBuilder.onConflict = vi.fn(() => ({ + ignore: vi.fn(() => queryBuilder), + merge: vi.fn(() => queryBuilder), })) // Mock knex function and .table() - const mockKnex = Object.assign(jest.fn().mockReturnValue(queryBuilder), { - table: jest.fn().mockReturnValue(queryBuilder), + const mockKnex = Object.assign(vi.fn().mockReturnValue(queryBuilder), { + table: vi.fn().mockReturnValue(queryBuilder), }) as unknown as Knex return mockKnex diff --git a/src/test/object-list-v2.test.ts b/src/test/object-list-v2.test.ts new file mode 100644 index 000000000..ef467cc54 --- /dev/null +++ b/src/test/object-list-v2.test.ts @@ -0,0 +1,701 @@ +import { randomUUID } from 'node:crypto' +import { ListObjectsV2Result } from '@storage/object' +import { FastifyInstance } from 'fastify' +import { Knex } from 'knex' +import app from '../app' +import { getConfig } from '../config' +import { useMockObject, useMockQueue } from './common' + +const { serviceKeyAsync } = getConfig() +let appInstance: FastifyInstance +let serviceKey: string = '' + +let tnx: Knex.Transaction | undefined + +useMockObject() +useMockQueue() + +beforeEach(() => { + getConfig({ reload: true }) + appInstance = app() +}) + +afterEach(async () => { + if (tnx) { + await tnx.commit() + } + await appInstance.close() +}) + +const LIST_V2_BUCKET = 'list-v2-sorting-test-bucket' + +// Helper to convert a number into a 3-letter string (aaa ... zzz with some uppercase) +function toName(n: number): string { + const a = 97 // 'a' + const first = String.fromCharCode(a + (Math.floor(n / (26 * 26)) % 26)) + const second = String.fromCharCode(a + (Math.floor(n / 26) % 26)) + const third = String.fromCharCode(a + (n % 26)) + const name = first + second + third + if (n >= 1 && n <= 3) { + return name.toUpperCase() + } + return name +} + +function createUpload(name: string, content: string) { + return new File([content], name) +} + +function shuffleArray(array: T[]): T[] { + const shuffled = [...array] + for (let i = shuffled.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + ;[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]] + } + return shuffled +} + +const SORTED_OBJECTS: string[] = [] +const SORTED_FOLDERS: string[] = [] +const NESTED_OBJECTS: string[] = [] +const PREFIX_OBJECTS: Record = + {} +const TEST_PREFIX = 'aal' + +// Generate sorted list of objects/folders +for (let i = 0; i < 30; i++) { + const name = toName(i) + if (i > 5) { + SORTED_OBJECTS.push(name + '.txt') + } + if (i < 18) { + const folder = name + '/' + SORTED_FOLDERS.push(folder) + + const nestedCount = name === TEST_PREFIX ? 9 : 3 + for (let j = 0; j < nestedCount; j++) { + const objectPath = `${folder}dummy-${name}-${j}.txt` + NESTED_OBJECTS.push(objectPath) + PREFIX_OBJECTS[folder] ??= { sorted: [], created: [], updated: [] } + PREFIX_OBJECTS[folder].sorted.push(objectPath) + } + } +} + +// Sort the arrays since uppercase letters may have changed the order +SORTED_OBJECTS.sort() +SORTED_FOLDERS.sort() +for (const folder of Object.keys(PREFIX_OBJECTS)) { + PREFIX_OBJECTS[folder].sorted.sort() +} + +// Combine all paths for creation +const ALL_PATHS = [...SORTED_OBJECTS, ...NESTED_OBJECTS].sort() + +// Lists of objects and folders in sorted +const CREATION_ORDER_OBJECTS: string[] = [] +const UPDATE_ORDER_OBJECTS: string[] = [] +const CREATION_ORDER_FOLDERS: string[] = [] +const CREATION_ORDER_ALL: string[] = [] +const UPDATE_ORDER_ALL: string[] = [] + +beforeAll(async () => { + serviceKey = await serviceKeyAsync + appInstance = app() + + // Create bucket + await appInstance.inject({ + method: 'POST', + url: `/bucket`, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + payload: { + name: LIST_V2_BUCKET, + }, + }) + + const shuffledPaths = shuffleArray(ALL_PATHS) + + // Create all objects in random order + for (const path of shuffledPaths) { + if (path.includes('/')) { + // root folders in creation order + const rootFolder = path.split('/')[0] + '/' + if (!CREATION_ORDER_FOLDERS.includes(rootFolder)) { + CREATION_ORDER_FOLDERS.push(rootFolder) + } + PREFIX_OBJECTS[rootFolder].created.push(path) + PREFIX_OBJECTS[rootFolder].updated.push(path) + } else { + // root objects in creation order + CREATION_ORDER_OBJECTS.push(path) + UPDATE_ORDER_OBJECTS.push(path) + } + CREATION_ORDER_ALL.push(path) + UPDATE_ORDER_ALL.push(path) + await appInstance.inject({ + method: 'POST', + url: `/object/${LIST_V2_BUCKET}/${path}`, + payload: createUpload(path, 'test content'), + headers: { + authorization: serviceKey, + }, + }) + } + + const headers = { + authorization: serviceKey, + 'x-upsert': 'true', + } + + // update a few objects to make updated_at different than created_at + for (let i = 0; i < 10; i++) { + const firstItem = UPDATE_ORDER_OBJECTS.shift()! + await appInstance.inject({ + method: 'POST', + url: `/object/${LIST_V2_BUCKET}/${firstItem}`, + payload: createUpload(firstItem, 'test content'), + headers, + }) + UPDATE_ORDER_OBJECTS.push(firstItem) + + // re-arrange item in flat object list to updated order + UPDATE_ORDER_ALL.splice(UPDATE_ORDER_ALL.indexOf(firstItem), 1) + UPDATE_ORDER_ALL.push(firstItem) + } + + // switch to Object.entries(PREFIX_OBJECTS) to test all prefixes + const prefixRoot = TEST_PREFIX + '/' + const obj = PREFIX_OBJECTS[prefixRoot] + const firstPrefixItem = obj.updated.shift()! + await appInstance.inject({ + method: 'POST', + url: `/object/${LIST_V2_BUCKET}/${firstPrefixItem}`, + payload: createUpload(firstPrefixItem, 'test content'), + headers, + }) + PREFIX_OBJECTS[prefixRoot].updated.push(firstPrefixItem) + + // re-arrange item in flat object list to updated order of nested item + UPDATE_ORDER_ALL.splice(UPDATE_ORDER_ALL.indexOf(firstPrefixItem), 1) + UPDATE_ORDER_ALL.push(firstPrefixItem) + + await appInstance.close() +}, 300000) + +afterAll(async () => { + appInstance = app() + + // Empty the bucket + await appInstance.inject({ + method: 'POST', + url: `/bucket/${LIST_V2_BUCKET}/empty`, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + // Delete the bucket + await appInstance.inject({ + method: 'DELETE', + url: `/bucket/${LIST_V2_BUCKET}`, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + await appInstance.close() +}) + +describe('objects - list v2 sorting tests', () => { + const TEST_CASES = [ + // WITH DELIMITER + { + desc: 'with delimiter - default sorting (name asc)', + options: { + with_delimiter: true, + }, + expected: { objects: SORTED_OBJECTS, folders: SORTED_FOLDERS }, + }, + { + desc: 'with delimiter - name desc', + options: { + with_delimiter: true, + sortBy: { + column: 'name', + order: 'desc', + }, + }, + expected: { + objects: SORTED_OBJECTS.slice().reverse(), + folders: SORTED_FOLDERS.slice().reverse(), + }, + }, + + { + desc: 'with delimiter - created asc', + options: { + with_delimiter: true, + sortBy: { + column: 'created_at', + order: 'asc', + }, + }, + expected: { + get objects() { + return CREATION_ORDER_OBJECTS + }, + get folders() { + return CREATION_ORDER_FOLDERS + }, + }, + }, + { + desc: 'with delimiter - created desc', + options: { + with_delimiter: true, + sortBy: { + column: 'created_at', + order: 'desc', + }, + }, + expected: { + get objects() { + return CREATION_ORDER_OBJECTS.slice().reverse() + }, + get folders() { + return CREATION_ORDER_FOLDERS.slice().reverse() + }, + }, + }, + + { + desc: 'with delimiter - updated asc', + options: { + with_delimiter: true, + sortBy: { + column: 'updated_at', + order: 'asc', + }, + }, + expected: { + get objects() { + return UPDATE_ORDER_OBJECTS + }, + get folders() { + return CREATION_ORDER_FOLDERS + }, + }, + }, + { + desc: 'with delimiter - updated desc', + options: { + with_delimiter: true, + sortBy: { + column: 'updated_at', + order: 'desc', + }, + }, + expected: { + get objects() { + return UPDATE_ORDER_OBJECTS.slice().reverse() + }, + get folders() { + return CREATION_ORDER_FOLDERS.slice().reverse() + }, + }, + }, + + // WITHOUT DELIMITER + { + desc: 'without delimiter - default sorting (name asc)', + options: { + with_delimiter: false, + }, + expected: { objects: ALL_PATHS, folders: [] }, + }, + { + desc: 'without delimiter - name desc without delimiter', + options: { + with_delimiter: false, + sortBy: { + column: 'name', + order: 'desc', + }, + }, + expected: { objects: ALL_PATHS.slice().reverse(), folders: [] }, + }, + + { + desc: 'without delimiter - created asc', + options: { + with_delimiter: false, + sortBy: { + column: 'created_at', + order: 'asc', + }, + }, + expected: { + get objects() { + return CREATION_ORDER_ALL + }, + folders: [], + }, + }, + { + desc: 'without delimiter - created desc', + options: { + with_delimiter: false, + sortBy: { + column: 'created_at', + order: 'desc', + }, + }, + expected: { + get objects() { + return CREATION_ORDER_ALL.slice().reverse() + }, + folders: [], + }, + }, + + { + desc: 'without delimiter - updated asc', + options: { + with_delimiter: false, + sortBy: { + column: 'updated_at', + order: 'asc', + }, + }, + expected: { + get objects() { + return UPDATE_ORDER_ALL + }, + folders: [], + }, + }, + { + desc: 'without delimiter - updated desc', + options: { + with_delimiter: false, + sortBy: { + column: 'updated_at', + order: 'desc', + }, + }, + expected: { + get objects() { + return UPDATE_ORDER_ALL.slice().reverse() + }, + folders: [], + }, + }, + + // WITH PREFIX + { + desc: `prefix - with delimiter - default sorting (name asc)`, + options: { + with_delimiter: true, + prefix: TEST_PREFIX + '/', + }, + expected: { objects: PREFIX_OBJECTS[TEST_PREFIX + '/'].sorted, folders: [] }, + }, + { + desc: 'prefix - with delimiter - name desc', + options: { + with_delimiter: true, + prefix: TEST_PREFIX + '/', + sortBy: { + column: 'name', + order: 'desc', + }, + }, + expected: { + objects: PREFIX_OBJECTS[TEST_PREFIX + '/'].sorted.slice().reverse(), + folders: [], + }, + }, + + { + desc: 'prefix - with delimiter - created asc', + options: { + with_delimiter: true, + prefix: TEST_PREFIX + '/', + sortBy: { + column: 'created_at', + order: 'asc', + }, + }, + expected: { + get objects() { + return PREFIX_OBJECTS[TEST_PREFIX + '/'].created + }, + folders: [], + }, + }, + { + desc: 'prefix - with delimiter - created desc', + options: { + with_delimiter: true, + prefix: TEST_PREFIX + '/', + sortBy: { + column: 'created_at', + order: 'desc', + }, + }, + expected: { + get objects() { + return PREFIX_OBJECTS[TEST_PREFIX + '/'].created.slice().reverse() + }, + folders: [], + }, + }, + + { + desc: 'prefix - with delimiter - updated asc', + options: { + with_delimiter: true, + prefix: TEST_PREFIX + '/', + sortBy: { + column: 'updated_at', + order: 'asc', + }, + }, + expected: { + get objects() { + return PREFIX_OBJECTS[TEST_PREFIX + '/'].updated + }, + folders: [], + }, + }, + { + desc: 'prefix - with delimiter - updated desc', + options: { + with_delimiter: true, + prefix: TEST_PREFIX + '/', + sortBy: { + column: 'updated_at', + order: 'desc', + }, + }, + expected: { + get objects() { + return PREFIX_OBJECTS[TEST_PREFIX + '/'].updated.slice().reverse() + }, + folders: [], + }, + }, + + { + desc: 'prefix with slash - without delimiter', + options: { + with_delimiter: false, + prefix: TEST_PREFIX + '/', + }, + expected: { objects: PREFIX_OBJECTS[TEST_PREFIX + '/'].sorted, folders: [] }, + }, + + { + desc: 'prefix without slash - with delimiter', + options: { + with_delimiter: true, + prefix: TEST_PREFIX, + }, + expected: { objects: [TEST_PREFIX + '.txt'], folders: [TEST_PREFIX + '/'] }, + }, + + { + desc: 'prefix without slash - without delimiter', + options: { + with_delimiter: false, + prefix: TEST_PREFIX, + }, + expected: { + objects: [TEST_PREFIX + '.txt', ...PREFIX_OBJECTS[TEST_PREFIX + '/'].sorted], + folders: [], + }, + }, + ] + + for (const { desc, options, expected } of TEST_CASES) { + test(desc + ' in correct order with pagination', async () => { + const limit = 5 + let cursor: string | undefined + let pageCount = 0 + let lastObjectIdx = -1 + let lastFolderIdx = -1 + let hasNext = false + + // Paginate through all results + do { + const response = await appInstance.inject({ + method: 'POST', + url: '/object/list-v2/' + LIST_V2_BUCKET, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + payload: { + ...options, + limit, + cursor, + }, + }) + + const data = response.json() + expect(response.statusCode).toBe(200) + + // Verify each object is the expected next one in sequence + data.objects.forEach((obj) => { + const expObj = expected.objects[++lastObjectIdx] + expect(obj.name).toBe(expObj) + }) + + // Verify each folder is the expected next one in sequence + data.folders.forEach((folder) => { + const expFolder = expected.folders[++lastFolderIdx] + expect(folder.name).toBe(expFolder) + }) + pageCount++ + + hasNext = data.hasNext ?? false + if (!hasNext) { + expect(data.nextCursor).toBeUndefined() + } else { + cursor = data.nextCursor as string + expect(cursor).toBeDefined() + } + } while (hasNext) + + // Verify we processed all expected items + expect(lastObjectIdx).toBe(expected.objects.length - 1) + expect(lastFolderIdx).toBe(expected.folders.length - 1) + expect(pageCount).toBe(Math.ceil((expected.objects.length + expected.folders.length) / limit)) + }) + } +}) + +const LIST_V2_WILDCARD_BUCKET = `list-v2-wildcard-${randomUUID()}` + +describe('objects - list v2 prefix wildcard handling', () => { + beforeAll(async () => { + appInstance = app() + await appInstance.inject({ + method: 'POST', + url: `/bucket`, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + payload: { + name: LIST_V2_WILDCARD_BUCKET, + }, + }) + await appInstance.close() + }) + + afterAll(async () => { + appInstance = app() + await appInstance.inject({ + method: 'POST', + url: `/bucket/${LIST_V2_WILDCARD_BUCKET}/empty`, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + await appInstance.inject({ + method: 'DELETE', + url: `/bucket/${LIST_V2_WILDCARD_BUCKET}`, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + await appInstance.close() + }) + + test('treats % as a literal character in list-v2 prefix filters', async () => { + const runId = Date.now().toString(36) + const firstObject = `percent-${runId}/first.txt` + const secondObject = `percent-${runId}/second.txt` + + await appInstance.inject({ + method: 'POST', + url: `/object/${LIST_V2_WILDCARD_BUCKET}/${firstObject}`, + payload: createUpload('first.txt', 'first'), + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + await appInstance.inject({ + method: 'POST', + url: `/object/${LIST_V2_WILDCARD_BUCKET}/${secondObject}`, + payload: createUpload('second.txt', 'second'), + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + const response = await appInstance.inject({ + method: 'POST', + url: `/object/list-v2/${LIST_V2_WILDCARD_BUCKET}`, + payload: { + with_delimiter: false, + prefix: '%', + limit: 100, + }, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + expect(response.statusCode).toBe(200) + const data = response.json() + expect(data.folders).toHaveLength(0) + expect(data.objects).toHaveLength(0) + }) + + test('treats _ as a literal character in list-v2 prefix filters', async () => { + const runId = randomUUID() + const literalMatch = `wild_${runId}/hit.txt` + const wildcardOnlyMatch = `wildX${runId}/miss.txt` + + await appInstance.inject({ + method: 'POST', + url: `/object/${LIST_V2_WILDCARD_BUCKET}/${literalMatch}`, + payload: createUpload('hit.txt', 'hit'), + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + await appInstance.inject({ + method: 'POST', + url: `/object/${LIST_V2_WILDCARD_BUCKET}/${wildcardOnlyMatch}`, + payload: createUpload('miss.txt', 'miss'), + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + const response = await appInstance.inject({ + method: 'POST', + url: `/object/list-v2/${LIST_V2_WILDCARD_BUCKET}`, + payload: { + with_delimiter: false, + prefix: `wild_${runId}/`, + limit: 100, + }, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + expect(response.statusCode).toBe(200) + const data = response.json() + expect(data.folders).toHaveLength(0) + expect(data.objects.map((obj) => obj.name)).toEqual([literalMatch]) + }) +}) diff --git a/src/test/object.test.ts b/src/test/object.test.ts index 71bf5a203..da1d9d1e3 100644 --- a/src/test/object.test.ts +++ b/src/test/object.test.ts @@ -1,16 +1,27 @@ -'use strict' +vi.hoisted(() => { + process.env.PG_QUEUE_ENABLE = 'true' +}) +import { + generateHS512JWK, + getMaxNumericJWTExpiration, + SignedToken, + signJWT, + verifyJWT, +} from '@internal/auth' +import { getPostgresConnection, getServiceKeyUser } from '@internal/database' +import { ErrorCode, StorageBackendError } from '@internal/errors' +import { randomUUID } from 'crypto' +import { FastifyInstance } from 'fastify' import FormData from 'form-data' import fs from 'fs' +import { Knex } from 'knex' import app from '../app' import { getConfig, JwksConfig, JwksConfigKeyOCT, mergeConfig } from '../config' -import { generateHS512JWK, SignedToken, signJWT, verifyJWT } from '@internal/auth' -import { Obj, backends } from '../storage' +import { backends, Obj } from '../storage' +import { ObjectAdminDelete } from '../storage/events' import { useMockObject, useMockQueue } from './common' -import { getServiceKeyUser, getPostgresConnection } from '@internal/database' -import { Knex } from 'knex' -import { ErrorCode, StorageBackendError } from '@internal/errors' -import { FastifyInstance } from 'fastify' +import { withDeleteEnabled } from './utils/storage' const { jwtSecret, serviceKeyAsync, tenantId } = getConfig() const anonKey = process.env.ANON_KEY || '' @@ -61,8 +72,9 @@ describe('testing GET object', () => { }) expect(response.statusCode).toBe(200) expect(response.headers['etag']).toBe('abc') + expect(response.headers['x-robots-tag']).toBe('none') expect(response.headers['last-modified']).toBe('Thu, 12 Aug 2021 16:00:00 GMT') - expect(S3Backend.prototype.getObject).toBeCalled() + expect(S3Backend.prototype.getObject).toHaveBeenCalled() }) test('check if RLS policies are respected: authenticated user is able to read authenticated resource without /authenticated prefix', async () => { @@ -76,11 +88,11 @@ describe('testing GET object', () => { expect(response.statusCode).toBe(200) expect(response.headers['etag']).toBe('abc') expect(response.headers['last-modified']).toBe('Thu, 12 Aug 2021 16:00:00 GMT') - expect(S3Backend.prototype.getObject).toBeCalled() + expect(S3Backend.prototype.getObject).toHaveBeenCalled() }) test('forward 304 and If-Modified-Since/If-None-Match headers', async () => { - const mockGetObject = jest.spyOn(S3Backend.prototype, 'getObject') + const mockGetObject = vi.spyOn(S3Backend.prototype, 'getObject') mockGetObject.mockRejectedValue({ $metadata: { httpStatusCode: 304, @@ -178,7 +190,7 @@ describe('testing GET object', () => { authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, }, }) - expect(S3Backend.prototype.getObject).toBeCalled() + expect(S3Backend.prototype.getObject).toHaveBeenCalled() expect(response.headers).toEqual( expect.objectContaining({ 'content-disposition': `attachment;`, @@ -194,7 +206,7 @@ describe('testing GET object', () => { authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, }, }) - expect(S3Backend.prototype.getObject).toBeCalled() + expect(S3Backend.prototype.getObject).toHaveBeenCalled() expect(response.headers).toEqual( expect.objectContaining({ 'content-disposition': `attachment; filename=testname.png; filename*=UTF-8''testname.png`, @@ -289,7 +301,7 @@ describe('testing POST object via multipart upload', () => { payload: form, }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.uploadObject).toBeCalled() + expect(S3Backend.prototype.uploadObject).toHaveBeenCalled() expect(await response.json()).toEqual( expect.objectContaining({ Id: expect.any(String), @@ -551,7 +563,44 @@ describe('testing POST object via multipart upload', () => { }) }) - test('return 422 when uploading an object with a not allowed mime-type', async () => { + test('can create an empty folder when mime-type is set', async () => { + const form = new FormData() + const headers = Object.assign({}, form.getHeaders(), { + authorization: `Bearer ${await serviceKeyAsync}`, + 'x-upsert': 'true', + }) + + form.append('file', Buffer.alloc(0)) + + const response = await appInstance.inject({ + method: 'POST', + url: '/object/public-limit-mime-types/nested/.emptyFolderPlaceholder', + headers, + payload: form, + }) + expect(response.statusCode).toBe(200) + expect(S3Backend.prototype.uploadObject).toHaveBeenCalled() + }) + + test('cannot create an empty folder with more than 0kb', async () => { + const form = new FormData() + const headers = Object.assign({}, form.getHeaders(), { + authorization: `Bearer ${await serviceKeyAsync}`, + 'x-upsert': 'true', + }) + + form.append('file', Buffer.alloc(1)) + + const response = await appInstance.inject({ + method: 'POST', + url: '/object/public-limit-mime-types/nested-2/.emptyFolderPlaceholder', + headers, + payload: form, + }) + expect(response.statusCode).toBe(400) + }) + + test('return 400 when uploading an object with a not allowed mime-type (binary path)', async () => { const form = new FormData() form.append('file', fs.createReadStream(`./src/test/assets/sadcat.jpg`)) const headers = Object.assign({}, form.getHeaders(), { @@ -575,50 +624,61 @@ describe('testing POST object via multipart upload', () => { expect(S3Backend.prototype.uploadObject).not.toHaveBeenCalled() }) - test('can create an empty folder when mime-type is set', async () => { + test('return 400 when uploading a multipart form-data object with a not allowed mime-type', async () => { const form = new FormData() + form.append('file', fs.createReadStream(`./src/test/assets/sadcat.jpg`)) + form.append('contentType', 'image/png') const headers = Object.assign({}, form.getHeaders(), { authorization: `Bearer ${await serviceKeyAsync}`, 'x-upsert': 'true', }) - form.append('file', Buffer.alloc(0)) - const response = await appInstance.inject({ method: 'POST', - url: '/object/public-limit-mime-types/nested/.emptyFolderPlaceholder', + url: '/object/public-limit-mime-types/sadcat-upload23.png', headers, payload: form, }) - expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.uploadObject).toHaveBeenCalled() + expect(response.statusCode).toBe(400) + expect(await response.json()).toEqual({ + error: 'invalid_mime_type', + message: `mime type image/png is not supported`, + statusCode: '415', + }) + expect(S3Backend.prototype.uploadObject).not.toHaveBeenCalled() }) - test('cannot create an empty folder with more than 0kb', async () => { + test('return 400 when uploading an object with a malformed mime-type', async () => { const form = new FormData() + form.append('file', fs.createReadStream(`./src/test/assets/sadcat.jpg`)) const headers = Object.assign({}, form.getHeaders(), { authorization: `Bearer ${await serviceKeyAsync}`, 'x-upsert': 'true', + 'content-type': 'thisisnotarealmimetype', }) - form.append('file', Buffer.alloc(1)) - const response = await appInstance.inject({ method: 'POST', - url: '/object/public-limit-mime-types/nested-2/.emptyFolderPlaceholder', + url: '/object/public-limit-mime-types/sadcat-upload23.png', headers, payload: form, }) expect(response.statusCode).toBe(400) + expect(await response.json()).toEqual({ + error: 'invalid_mime_type', + message: 'Invalid Content-Type header', + statusCode: '415', + }) + expect(S3Backend.prototype.uploadObject).not.toHaveBeenCalled() }) - test('return 422 when uploading an object with a malformed mime-type', async () => { + test('return 400 when uploading an object with a content-type header containing tabs', async () => { const form = new FormData() form.append('file', fs.createReadStream(`./src/test/assets/sadcat.jpg`)) const headers = Object.assign({}, form.getHeaders(), { authorization: `Bearer ${await serviceKeyAsync}`, 'x-upsert': 'true', - 'content-type': 'thisisnotarealmimetype', + 'content-type': 'image/\tjpg', }) const response = await appInstance.inject({ @@ -630,7 +690,7 @@ describe('testing POST object via multipart upload', () => { expect(response.statusCode).toBe(400) expect(await response.json()).toEqual({ error: 'invalid_mime_type', - message: `mime type thisisnotarealmimetype is not supported`, + message: 'Invalid Content-Type header', statusCode: '415', }) expect(S3Backend.prototype.uploadObject).not.toHaveBeenCalled() @@ -700,7 +760,7 @@ describe('testing POST object via multipart upload', () => { test('should not add row to database if upload fails', async () => { // Mock S3 upload failure. - jest.spyOn(S3Backend.prototype, 'uploadObject').mockRejectedValue( + vi.spyOn(S3Backend.prototype, 'uploadObject').mockRejectedValue( StorageBackendError.fromError({ name: 'S3ServiceException', message: 'Unknown error', @@ -773,7 +833,7 @@ describe('testing POST object via binary upload', () => { payload: fs.createReadStream(path), }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.uploadObject).toBeCalled() + expect(S3Backend.prototype.uploadObject).toHaveBeenCalled() expect(await response.json()).toEqual( expect.objectContaining({ Id: expect.any(String), @@ -918,6 +978,59 @@ describe('testing POST object via binary upload', () => { ) }) + test('return 400 when a binary upload spoofs x-amz-decoded-content-length', async () => { + mergeConfig({ + uploadFileSizeLimit: 1, + }) + + const bucketId = `spoof-decoded-${randomUUID()}` + const superUser = await getServiceKeyUser(tenantId) + const db = await getPostgresConnection({ + superUser, + user: superUser, + tenantId, + host: 'localhost', + }) + const setupTx = await db.transaction() + await setupTx.table('buckets').insert({ + id: bucketId, + name: bucketId, + public: true, + file_size_limit: null, + allowed_mime_types: null, + type: 'STANDARD', + }) + await setupTx.commit() + await db.dispose() + + const path = './src/test/assets/sadcat.jpg' + const { size } = fs.statSync(path) + + const headers = { + authorization: `Bearer ${await serviceKeyAsync}`, + 'Content-Length': size, + 'Content-Type': 'image/jpeg', + 'x-amz-decoded-content-length': '1', + } + + const response = await appInstance.inject({ + method: 'POST', + url: `/object/${bucketId}/public/sadcat-spoofed-decoded-length.jpg`, + headers, + payload: fs.createReadStream(path), + }) + expect(response.statusCode).toBe(400) + expect(response.body).toBe( + JSON.stringify({ + statusCode: '413', + error: 'Payload too large', + message: 'The object exceeded the maximum allowed size', + }) + ) + // Early size check in fileUploadFromRequest rejects before reaching the backend + expect(S3Backend.prototype.uploadObject).not.toHaveBeenCalled() + }) + test('return 400 when uploading to object with no file name', async () => { const path = './src/test/assets/sadcat.jpg' const { size } = fs.statSync(path) @@ -941,7 +1054,7 @@ describe('testing POST object via binary upload', () => { test('should not add row to database if upload fails', async () => { // Mock S3 upload failure. - jest.spyOn(S3Backend.prototype, 'uploadObject').mockRejectedValue( + vi.spyOn(S3Backend.prototype, 'uploadObject').mockRejectedValue( StorageBackendError.fromError({ name: 'S3ServiceException', message: 'Unknown error', @@ -1012,7 +1125,7 @@ describe('testing PUT object', () => { payload: form, }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.uploadObject).toBeCalled() + expect(S3Backend.prototype.uploadObject).toHaveBeenCalled() expect(await response.json()).toEqual( expect.objectContaining({ Id: expect.any(String), @@ -1112,7 +1225,7 @@ describe('testing PUT object via binary upload', () => { payload: fs.createReadStream(path), }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.uploadObject).toBeCalled() + expect(S3Backend.prototype.uploadObject).toHaveBeenCalled() expect(await response.json()).toEqual( expect.objectContaining({ Id: expect.any(String), @@ -1219,7 +1332,7 @@ describe('testing copy object', () => { }, }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.copyObject).toBeCalled() + expect(S3Backend.prototype.copyObject).toHaveBeenCalled() const jsonResponse = await response.json() expect(jsonResponse.Key).toBe(`bucket2/authenticated/casestudy11.png`) }) @@ -1239,7 +1352,7 @@ describe('testing copy object', () => { }, }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.copyObject).toBeCalled() + expect(S3Backend.prototype.copyObject).toHaveBeenCalled() const jsonResponse = await response.json() expect(jsonResponse.Key).toBe(`bucket3/authenticated/casestudy11.png`) @@ -1261,7 +1374,7 @@ describe('testing copy object', () => { }, }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.copyObject).toBeCalled() + expect(S3Backend.prototype.copyObject).toHaveBeenCalled() const jsonResponse = response.json() expect(jsonResponse.Key).toBe(`bucket2/authenticated/${copiedKey}`) @@ -1305,7 +1418,7 @@ describe('testing copy object', () => { }, }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.copyObject).toBeCalled() + expect(S3Backend.prototype.copyObject).toHaveBeenCalled() const parsedBody = JSON.parse(response.body) expect(parsedBody.Key).toBe(`bucket2/authenticated/${copiedKey}`) @@ -1354,7 +1467,7 @@ describe('testing copy object', () => { }, }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.copyObject).toBeCalled() + expect(S3Backend.prototype.copyObject).toHaveBeenCalled() const jsonResponse = response.json() expect(jsonResponse.Key).toBe(`bucket2/authenticated/${copiedKey}`) @@ -1466,7 +1579,7 @@ describe('testing delete object', () => { }, }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.deleteObject).toBeCalled() + expect(S3Backend.prototype.deleteObject).toHaveBeenCalled() }) test('check if RLS policies are respected: anon user is not able to delete authenticated resource', async () => { @@ -1531,7 +1644,7 @@ describe('testing deleting multiple objects', () => { }, }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.deleteObjects).toBeCalled() + expect(S3Backend.prototype.deleteObjects).toHaveBeenCalled() const result = JSON.parse(response.body) expect(result).toHaveLength(10001) @@ -1612,7 +1725,7 @@ describe('testing deleting multiple objects', () => { }, }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.deleteObjects).toBeCalled() + expect(S3Backend.prototype.deleteObjects).toHaveBeenCalled() const results = JSON.parse(response.body) expect(results).toHaveLength(1) expect(results[0].name).toBe('authenticated/delete-multiple7.png') @@ -1725,6 +1838,38 @@ describe('testing generating signed URL', () => { }) expect(response.statusCode).toBe(400) }) + + test('rejects oversized expiresIn values for signed URLs before jwt signing', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/object/sign/bucket2/authenticated/cat.jpg', + headers: { + authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, + }, + payload: { + expiresIn: 1e21, + }, + }) + + expect(response.statusCode).toBe(400) + expect(JSON.parse(response.body).message).toContain('expiresIn') + }) + + test('rejects expiresIn values above the current runtime maximum for signed URLs', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/object/sign/bucket2/authenticated/cat.jpg', + headers: { + authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, + }, + payload: { + expiresIn: getMaxNumericJWTExpiration() + 10, + }, + }) + + expect(response.statusCode).toBe(400) + expect(JSON.parse(response.body).message).toContain('expiresIn') + }) }) /** @@ -1872,13 +2017,15 @@ describe('testing uploading with generated signed upload URL', () => { expect(objectResponse?.owner).toBe(owner) // remove row to not to break other tests - await db - .from('objects') - .where({ - name: OBJECT_NAME, - bucket_id: BUCKET_ID, - }) - .delete() + await withDeleteEnabled(db, async (db) => { + await db + .from('objects') + .where({ + name: OBJECT_NAME, + bucket_id: BUCKET_ID, + }) + .delete() + }) }) test('upload object without a token', async () => { @@ -1927,7 +2074,7 @@ describe('testing uploading with generated signed upload URL', () => { const urlToSign = `${BUCKET_ID}/${OBJECT_NAME}` const owner = '317eadce-631a-4429-a0bb-f19a7a517b4a' - const jwtToken = await signJWT({ owner, url: urlToSign }, jwtSecret, -1) + const jwtToken = await signJWT({ owner, url: urlToSign }, jwtSecret, '-1s') const response = await appInstance.inject({ method: 'PUT', url: `/object/upload/sign/${urlToSign}?token=${jwtToken}`, @@ -2100,6 +2247,23 @@ describe('testing generating signed URLs', () => { const result = JSON.parse(response.body) expect(result[0].error).toBe('Either the object does not exist or you do not have access to it') }) + + test('rejects oversized expiresIn values for batch signed URLs before jwt signing', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/object/sign/bucket2', + headers: { + authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, + }, + payload: { + expiresIn: 1e21, + paths: ['authenticated/cat.jpg'], + }, + }) + + expect(response.statusCode).toBe(400) + expect(JSON.parse(response.body).message).toContain('expiresIn') + }) }) /** @@ -2119,6 +2283,7 @@ describe('testing retrieving signed URL', () => { url: `/object/sign/${urlToSign}?token=${jwtToken}`, }) expect(response.statusCode).toBe(200) + expect(response.headers['x-robots-tag']).toBe('none') expect(response.headers['etag']).toBe('abc') expect(response.headers['last-modified']).toBe('Thu, 12 Aug 2021 16:00:00 GMT') }) @@ -2139,7 +2304,7 @@ describe('testing retrieving signed URL', () => { }) test('forward 304 and If-Modified-Since/If-None-Match headers', async () => { - const mockGetObject = jest.spyOn(S3Backend.prototype, 'getObject') + const mockGetObject = vi.spyOn(S3Backend.prototype, 'getObject') mockGetObject.mockRejectedValue({ $metadata: { httpStatusCode: 304, @@ -2192,7 +2357,7 @@ describe('testing retrieving signed URL', () => { test('get object with an expired JWT', async () => { const urlToSign = 'bucket2/public/sadcat-upload.png' - const expiredJWT = await signJWT({ url: urlToSign }, jwtSecret, -1) + const expiredJWT = await signJWT({ url: urlToSign }, jwtSecret, '-1s') const response = await appInstance.inject({ method: 'GET', url: `/object/sign/${urlToSign}?token=${expiredJWT}`, @@ -2203,6 +2368,7 @@ describe('testing retrieving signed URL', () => { describe('testing move object', () => { test('check if RLS policies are respected: authenticated user is able to move an authenticated object', async () => { + const objectAdminDeleteSendSpy = vi.spyOn(ObjectAdminDelete, 'send') const response = await appInstance.inject({ method: 'POST', url: `/object/move`, @@ -2217,10 +2383,11 @@ describe('testing move object', () => { }) expect(response.statusCode).toBe(200) expect(S3Backend.prototype.copyObject).toHaveBeenCalled() - expect(S3Backend.prototype.deleteObjects).toHaveBeenCalled() + expect(objectAdminDeleteSendSpy).toHaveBeenCalled() }) test('can move objects across buckets respecting RLS', async () => { + const objectAdminDeleteSendSpy = vi.spyOn(ObjectAdminDelete, 'send') const response = await appInstance.inject({ method: 'POST', url: `/object/move`, @@ -2236,7 +2403,53 @@ describe('testing move object', () => { }) expect(response.statusCode).toBe(200) expect(S3Backend.prototype.copyObject).toHaveBeenCalled() - expect(S3Backend.prototype.deleteObjects).toHaveBeenCalled() + expect(objectAdminDeleteSendSpy).toHaveBeenCalled() + }) + + test('cross-bucket move rollback should cleanup destination bucket object', async () => { + const runId = randomUUID() + const sourceKey = `authenticated/move-orig-rollback-${runId}.png` + const destinationKey = `authenticated/move-new-rollback-${runId}.png` + const destinationBucket = 'bucket3' + const objectAdminDeleteSendSpy = vi.spyOn(ObjectAdminDelete, 'send') + + const seedTx = await getSuperuserPostgrestClient() + await seedTx.from('objects').insert({ + bucket_id: 'bucket2', + name: sourceKey, + owner: '317eadce-631a-4429-a0bb-f19a7a517b4a', + version: `rollback-version-${runId}`, + metadata: { mimetype: 'image/png', size: 1234 }, + }) + await seedTx.commit() + tnx = undefined + + vi.spyOn(S3Backend.prototype, 'headObject').mockRejectedValueOnce( + new Error('forced move failure') + ) + + const response = await appInstance.inject({ + method: 'POST', + url: `/object/move`, + payload: { + bucketId: 'bucket2', + sourceKey, + destinationBucket, + destinationKey, + }, + headers: { + authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, + }, + }) + + expect(response.statusCode).toBeGreaterThanOrEqual(400) + expect(S3Backend.prototype.copyObject).toHaveBeenCalled() + expect(objectAdminDeleteSendSpy).toHaveBeenCalledWith( + expect.objectContaining({ + name: destinationKey, + bucketId: destinationBucket, + }) + ) }) test('cannot move objects across buckets because RLS checks', async () => { @@ -2386,10 +2599,11 @@ describe('testing list objects', () => { }) expect(response.statusCode).toBe(200) const responseJSON = JSON.parse(response.body) as { name: string }[] - expect(responseJSON).toHaveLength(2) + expect(responseJSON).toHaveLength(3) const names = responseJSON.map((ele) => ele.name) expect(names).toContain('only_uid.jpg') expect(names).toContain('subfolder') + expect(names).toContain('UPPER-folder') }) test('searching a non existent prefix', async () => { @@ -2495,8 +2709,9 @@ describe('testing list objects', () => { expect(response.statusCode).toBe(200) const responseJSON = JSON.parse(response.body) expect(responseJSON).toHaveLength(2) - expect(responseJSON[0].name).toBe('sadcat-upload23.png') - expect(responseJSON[1].name).toBe('sadcat-upload.png') + // Byte order (COLLATE "C"): '.' (46) < '2' (50), so sadcat-upload.png < sadcat-upload23.png + expect(responseJSON[0].name).toBe('sadcat-upload.png') + expect(responseJSON[1].name).toBe('sadcat-upload23.png') }) test('test descending search sorting', async () => { @@ -2517,7 +2732,288 @@ describe('testing list objects', () => { expect(response.statusCode).toBe(200) const responseJSON = JSON.parse(response.body) expect(responseJSON).toHaveLength(2) - expect(responseJSON[0].name).toBe('sadcat-upload.png') - expect(responseJSON[1].name).toBe('sadcat-upload23.png') + // Byte order (COLLATE "C"): sadcat-upload23.png > sadcat-upload.png + expect(responseJSON[0].name).toBe('sadcat-upload23.png') + expect(responseJSON[1].name).toBe('sadcat-upload.png') + }) + + test('list-v1 should treat % as a literal character when using non-name sorting', async () => { + const runId = randomUUID() + const bucketName = 'bucket2' + const objectNames = [`percent-${runId}/first.txt`, `percent-${runId}/second.txt`] + + const seedTx = await getSuperuserPostgrestClient() + await seedTx.from('objects').insert( + objectNames.map((name, idx) => ({ + bucket_id: bucketName, + name, + owner: '317eadce-631a-4429-a0bb-f19a7a517b4a', + version: `${runId}-${idx}`, + metadata: { + eTag: `${runId}-${idx}`, + size: idx + 1, + mimetype: 'text/plain', + }, + })) + ) + await seedTx.commit() + tnx = undefined + + try { + const response = await appInstance.inject({ + method: 'POST', + url: '/object/list/bucket2', + payload: { + prefix: '%', + limit: 100, + offset: 0, + sortBy: { + column: 'created_at', + order: 'asc', + }, + }, + headers: { + authorization: `Bearer ${await serviceKeyAsync}`, + }, + }) + + expect(response.statusCode).toBe(200) + const responseJSON = response.json() + expect(responseJSON).toHaveLength(0) + } finally { + const cleanupTx = await getSuperuserPostgrestClient() + await withDeleteEnabled(cleanupTx, async (db) => { + await db + .from('objects') + .where({ bucket_id: bucketName }) + .whereIn('name', objectNames) + .delete() + }) + await cleanupTx.commit() + tnx = undefined + } + }) + + test('list-v1 should treat _ as a literal character when using non-name sorting', async () => { + const runId = randomUUID() + const bucketName = 'bucket2' + const literalMatch = `wild_${runId}/hit.txt` + const wildcardOnlyMatch = `wildX${runId}/miss.txt` + + const seedTx = await getSuperuserPostgrestClient() + await seedTx.from('objects').insert([ + { + bucket_id: bucketName, + name: literalMatch, + owner: '317eadce-631a-4429-a0bb-f19a7a517b4a', + version: `${runId}-literal`, + metadata: { + eTag: `${runId}-literal`, + size: 1, + mimetype: 'text/plain', + }, + }, + { + bucket_id: bucketName, + name: wildcardOnlyMatch, + owner: '317eadce-631a-4429-a0bb-f19a7a517b4a', + version: `${runId}-wildcard`, + metadata: { + eTag: `${runId}-wildcard`, + size: 2, + mimetype: 'text/plain', + }, + }, + ]) + await seedTx.commit() + tnx = undefined + + try { + const response = await appInstance.inject({ + method: 'POST', + url: '/object/list/bucket2', + payload: { + prefix: `wild_${runId}/`, + limit: 100, + offset: 0, + sortBy: { + column: 'created_at', + order: 'asc', + }, + }, + headers: { + authorization: `Bearer ${await serviceKeyAsync}`, + }, + }) + + expect(response.statusCode).toBe(200) + const responseJSON = response.json<{ name: string }[]>() + expect(responseJSON.map((obj) => obj.name)).toEqual(['hit.txt']) + } finally { + const cleanupTx = await getSuperuserPostgrestClient() + await withDeleteEnabled(cleanupTx, async (db) => { + await db + .from('objects') + .where({ bucket_id: bucketName }) + .whereIn('name', [literalMatch, wildcardOnlyMatch]) + .delete() + }) + await cleanupTx.commit() + tnx = undefined + } + }) +}) + +describe('x-robots-tag header', () => { + const X_ROBOTS_TEST_BUCKET = 'X_ROBOTS_TEST_BUCKET' + beforeAll(async () => { + appInstance = app() + await appInstance.inject({ + method: 'POST', + url: `/bucket`, + headers: { + authorization: `Bearer ${await serviceKeyAsync}`, + }, + payload: { + name: X_ROBOTS_TEST_BUCKET, + }, + }) + await appInstance.close() + }) + + afterAll(async () => { + appInstance = app() + await appInstance.inject({ + method: 'POST', + url: `/bucket/${X_ROBOTS_TEST_BUCKET}/empty`, + headers: { + authorization: `Bearer ${await serviceKeyAsync}`, + }, + }) + await appInstance.inject({ + method: 'DELETE', + url: `/bucket/${X_ROBOTS_TEST_BUCKET}`, + headers: { + authorization: `Bearer ${await serviceKeyAsync}`, + }, + }) + await appInstance.close() + }) + + test('defaults x-robots-tag header to none if not specified', async () => { + const objPath = `${X_ROBOTS_TEST_BUCKET}/test-file-1.txt` + + const createResponse = await appInstance.inject({ + method: 'POST', + url: `/object/${objPath}`, + payload: new File(['test'], 'file.txt'), + headers: { + authorization: `Bearer ${await serviceKeyAsync}`, + }, + }) + expect(createResponse.statusCode).toBe(200) + + const response = await appInstance.inject({ + method: 'GET', + url: `/object/authenticated/${objPath}`, + headers: { + authorization: `Bearer ${await serviceKeyAsync}`, + }, + }) + expect(response.statusCode).toBe(200) + expect(response.headers['x-robots-tag']).toBe('none') + }) + + test('uses provided x-robots-tag header if set', async () => { + const objPath = `${X_ROBOTS_TEST_BUCKET}/test-file-2.txt` + + const createResponse = await appInstance.inject({ + method: 'POST', + url: `/object/${objPath}`, + payload: new File(['test'], 'file.txt'), + headers: { + authorization: `Bearer ${await serviceKeyAsync}`, + 'x-robots-tag': 'all', + }, + }) + expect(createResponse.statusCode).toBe(200) + + const response = await appInstance.inject({ + method: 'GET', + url: `/object/authenticated/${objPath}`, + headers: { + authorization: `Bearer ${await serviceKeyAsync}`, + }, + }) + expect(response.statusCode).toBe(200) + expect(response.headers['x-robots-tag']).toBe('all') + }) + + test('updates x-robots-tag header on upsert', async () => { + const objPath = `${X_ROBOTS_TEST_BUCKET}/test-file-3.txt` + + const createResponse = await appInstance.inject({ + method: 'POST', + url: `/object/${objPath}`, + payload: new File(['test'], 'file.txt'), + headers: { + authorization: `Bearer ${await serviceKeyAsync}`, + 'x-robots-tag': 'max-snippet: 10, notranslate', + }, + }) + expect(createResponse.statusCode).toBe(200) + + const response = await appInstance.inject({ + method: 'GET', + url: `/object/authenticated/${objPath}`, + headers: { + authorization: `Bearer ${await serviceKeyAsync}`, + }, + }) + expect(response.statusCode).toBe(200) + expect(response.headers['x-robots-tag']).toBe('max-snippet: 10, notranslate') + + const createResponse2 = await appInstance.inject({ + method: 'POST', + url: `/object/${objPath}`, + payload: new File(['test'], 'file.txt'), + headers: { + authorization: `Bearer ${await serviceKeyAsync}`, + 'x-upsert': 'true', + 'x-robots-tag': 'nofollow', + }, + }) + expect(createResponse2.statusCode).toBe(200) + + const response2 = await appInstance.inject({ + method: 'GET', + url: `/object/authenticated/${objPath}`, + headers: { + authorization: `Bearer ${await serviceKeyAsync}`, + }, + }) + expect(response2.statusCode).toBe(200) + expect(response2.headers['x-robots-tag']).toBe('nofollow') + }) + + test('rejects invalid x-robots-tag header with proper error', async () => { + const objPath = `${X_ROBOTS_TEST_BUCKET}/test-file-invalid.txt` + + const createResponse = await appInstance.inject({ + method: 'POST', + url: `/object/${objPath}`, + payload: new File(['test'], 'file.txt'), + headers: { + authorization: `Bearer ${await serviceKeyAsync}`, + 'x-robots-tag': 'invalidrule', + }, + }) + + expect(createResponse.statusCode).toBe(400) + expect(createResponse.json()).toMatchObject({ + statusCode: '400', + error: 'invalid_x_robots_tag', + message: 'Invalid X-Robots-Tag header: Invalid X-Robots-Tag rule: "invalidrule"', + }) }) }) diff --git a/src/test/operation-helpers.test.ts b/src/test/operation-helpers.test.ts new file mode 100644 index 000000000..ec3fe051c --- /dev/null +++ b/src/test/operation-helpers.test.ts @@ -0,0 +1,147 @@ +import { useStorage } from './utils/storage' + +describe('Storage operation helpers', () => { + const tHelper = useStorage() + + async function selectAllowed( + sql: string, + bindings: unknown[] = [], + currentOperation?: string + ): Promise { + const db = tHelper.database.connection.pool.acquire() + const tnx = await db.transaction() + + try { + if (currentOperation) { + await tnx.raw(`SELECT set_config('storage.operation', ?, true)`, [currentOperation]) + } + + const result = await tnx.raw(sql, bindings) + return result.rows[0].allowed + } finally { + if (!tnx.isCompleted()) { + await tnx.rollback() + } + } + } + + it('matches canonical operations through short and full names', async () => { + await expect( + selectAllowed( + `SELECT storage.allow_only_operation(?) AS allowed`, + ['storage.object.list'], + 'storage.object.list' + ) + ).resolves.toBe(true) + + await expect( + selectAllowed( + `SELECT storage.allow_only_operation(?) AS allowed`, + ['object.list'], + 'storage.object.list' + ) + ).resolves.toBe(true) + + await expect( + selectAllowed( + `SELECT storage.allow_only_operation(?) AS allowed`, + ['object.get'], + 'storage.object.list' + ) + ).resolves.toBe(false) + }) + + it('keeps compatibility with current bare object.* route operation values', async () => { + await expect( + selectAllowed( + `SELECT storage.allow_only_operation(?) AS allowed`, + ['object.get_authenticated_info'], + 'object.get_authenticated_info' + ) + ).resolves.toBe(true) + + await expect( + selectAllowed( + `SELECT storage.allow_only_operation(?) AS allowed`, + ['storage.object.get_authenticated_info'], + 'object.get_authenticated_info' + ) + ).resolves.toBe(true) + }) + + it('returns false when the current operation is unset or the input is empty', async () => { + await expect( + selectAllowed(`SELECT storage.allow_only_operation(?) AS allowed`, ['object.list']) + ).resolves.toBe(false) + + await expect( + selectAllowed( + `SELECT storage.allow_only_operation(?) AS allowed`, + [''], + 'storage.object.list' + ) + ).resolves.toBe(false) + }) + + it('matches any provided operation without prefix semantics', async () => { + await expect( + selectAllowed( + `SELECT storage.allow_any_operation(ARRAY[?, ?]::text[]) AS allowed`, + ['bucket.get', 'storage.object.list'], + 'storage.object.list' + ) + ).resolves.toBe(true) + + await expect( + selectAllowed( + `SELECT storage.allow_any_operation(ARRAY[?]::text[]) AS allowed`, + ['object'], + 'storage.object.list' + ) + ).resolves.toBe(false) + + await expect( + selectAllowed( + `SELECT storage.allow_any_operation(ARRAY[]::text[]) AS allowed`, + [], + 'storage.object.list' + ) + ).resolves.toBe(false) + }) + + it('ignores null and empty entries when matching any operation', async () => { + await expect( + selectAllowed( + `SELECT storage.allow_any_operation(ARRAY[?, ?, ?]::text[]) AS allowed`, + [null, '', 'storage.object.list'], + 'storage.object.list' + ) + ).resolves.toBe(true) + + await expect( + selectAllowed( + `SELECT storage.allow_any_operation(ARRAY[?, ?]::text[]) AS allowed`, + [null, ''], + 'storage.object.list' + ) + ).resolves.toBe(false) + + await expect( + selectAllowed( + `SELECT storage.allow_any_operation(NULL::text[]) AS allowed`, + [], + 'object.list' + ) + ).resolves.toBe(false) + }) + + it('keeps bare object.* compatibility for allow_any_operation', async () => { + await expect( + selectAllowed( + `SELECT storage.allow_any_operation(ARRAY[?, ?]::text[]) AS allowed`, + ['bucket.get', 'storage.object.get_authenticated_info'], + 'object.get_authenticated_info' + ) + ).resolves.toBe(true) + }) +}) diff --git a/src/test/pool.database.test.ts b/src/test/pool-database.test.ts similarity index 100% rename from src/test/pool.database.test.ts rename to src/test/pool-database.test.ts index d47e97a11..a3eca8f50 100644 --- a/src/test/pool.database.test.ts +++ b/src/test/pool-database.test.ts @@ -1,6 +1,6 @@ +import { getServiceKeyUser } from '@internal/database' import { PoolManager } from '@internal/database/pool' import { getConfig } from '../config' -import { getServiceKeyUser } from '@internal/database' const { databaseURL, databasePoolURL, tenantId } = getConfig() diff --git a/src/test/query-abort-signal.test.ts b/src/test/query-abort-signal.test.ts new file mode 100644 index 000000000..2e9f8dde5 --- /dev/null +++ b/src/test/query-abort-signal.test.ts @@ -0,0 +1,265 @@ +import { getServiceKeyUser } from '@internal/database' +import { TenantConnection } from '@internal/database/connection' +import { PoolManager } from '@internal/database/pool' +import { getConfig } from '../config' + +const { databaseURL, databasePoolURL, tenantId } = getConfig() +type TestPool = ReturnType +type TestConnection = ReturnType + +describe('Query Abort Signal', () => { + let poolManager: PoolManager + let superUser: Awaited> + + beforeAll(async () => { + superUser = await getServiceKeyUser(tenantId) + poolManager = new PoolManager() + }) + + async function withIsolatedConnection(run: (conn: TestConnection) => Promise) { + // Force fresh because single-use external requests still reuse + // an already cached pool for the same tenant. + await poolManager.destroy(tenantId) + + // Use an uncached single-use pool so aborted queries can't + // leak disposed connections into the next test case while Knex + // finishes tearing them down in the background. + const pool = poolManager.getPool({ + tenantId, + isSingleUse: true, + isExternalPool: true, + maxConnections: 1, + dbUrl: databasePoolURL || databaseURL, + user: superUser, + superUser, + }) + + const conn = pool.acquire() + + try { + await conn.raw('SELECT 1') + return await run(conn) + } finally { + try { + await pool.destroy() + } finally { + await poolManager.destroy(tenantId) + } + } + } + + describe('abortOnSignal on Raw queries', () => { + it('should execute query normally when signal is not aborted', async () => { + await withIsolatedConnection(async (conn) => { + const controller = new AbortController() + + const result = await conn.raw('SELECT 1 as test').abortOnSignal(controller.signal) + + expect(result.rows[0].test).toEqual(1) + }) + }) + + it('should abort query when signal is aborted before execution', async () => { + await withIsolatedConnection(async (conn) => { + const controller = new AbortController() + + // Abort before creating the query + controller.abort() + + await expect( + Promise.resolve().then(() => + conn.raw('SELECT 1 as test').abortOnSignal(controller.signal) + ) + ).rejects.toThrow('Signal is already aborted') + }) + }) + + it('should abort a long-running query when signal is aborted', async () => { + await withIsolatedConnection(async (conn) => { + const controller = new AbortController() + + // Start a slow query (pg_sleep for 10 seconds) + const queryPromise = conn.raw('SELECT pg_sleep(10)').abortOnSignal(controller.signal) + + // Abort after a short delay + const abortTimeout = setTimeout(() => controller.abort(), 100) + + try { + await expect(queryPromise).rejects.toMatchObject({ + name: 'AbortError', + code: 'ABORT_ERR', + message: 'Query was aborted', + }) + } finally { + clearTimeout(abortTimeout) + } + }) + }) + + it('should reject with AbortError containing correct properties', async () => { + await withIsolatedConnection(async (conn) => { + const controller = new AbortController() + + const queryPromise = conn.raw('SELECT pg_sleep(5)').abortOnSignal(controller.signal) + + const abortTimeout = setTimeout(() => controller.abort(), 50) + + try { + await queryPromise + throw new Error('Expected query to be aborted') + } catch (error: unknown) { + expect(error).toMatchObject({ + name: 'AbortError', + code: 'ABORT_ERR', + message: 'Query was aborted', + }) + } finally { + clearTimeout(abortTimeout) + } + }) + }) + + it('should throw error for invalid signal parameter', async () => { + await withIsolatedConnection(async (conn) => { + const invalidSignal = 'not a signal' as unknown as AbortSignal + + expect(() => { + conn.raw('SELECT 1').abortOnSignal(invalidSignal) + }).toThrow('Expected signal to be an instance of AbortSignal') + }) + }) + }) + + describe('abortOnSignal on Query Builder', () => { + it('should execute query normally when signal is not aborted', async () => { + await withIsolatedConnection(async (conn) => { + const controller = new AbortController() + + const result = await conn.select(conn.raw('1 as test')).abortOnSignal(controller.signal) + + expect(result[0].test).toEqual(1) + }) + }) + + it('should abort query builder query when signal is aborted', async () => { + await withIsolatedConnection(async (conn) => { + const controller = new AbortController() + + const queryPromise = conn.select(conn.raw('pg_sleep(10)')).abortOnSignal(controller.signal) + + const abortTimeout = setTimeout(() => controller.abort(), 100) + + try { + await expect(queryPromise).rejects.toMatchObject({ + name: 'AbortError', + }) + } finally { + clearTimeout(abortTimeout) + } + }) + }) + }) + + describe('abortOnSignal with timeout', () => { + it('should work with both timeout and abortSignal', async () => { + await withIsolatedConnection(async (conn) => { + const controller = new AbortController() + + const queryPromise = conn + .raw('SELECT pg_sleep(10)') + .timeout(5000, { cancel: true }) + .abortOnSignal(controller.signal) + + // Abort should win over timeout since we abort immediately + const abortTimeout = setTimeout(() => controller.abort(), 50) + + try { + await expect(queryPromise).rejects.toMatchObject({ + name: 'AbortError', + }) + } finally { + clearTimeout(abortTimeout) + } + }) + }) + }) +}) + +describe('Statement Timeout', () => { + it('should apply SET LOCAL statement_timeout in transactions', async () => { + const superUser = await getServiceKeyUser(tenantId) + const poolManager = new PoolManager() + const pool = poolManager.getPool({ + tenantId, + isExternalPool: true, + maxConnections: 2, + dbUrl: databasePoolURL || databaseURL, + user: superUser, + superUser, + }) + + const connection = new TenantConnection(pool, { + tenantId, + isExternalPool: true, + maxConnections: 2, + dbUrl: databasePoolURL || databaseURL, + user: superUser, + superUser, + }) + + const tnx = await connection.transaction() + + try { + // Check that statement_timeout is set + const result = await tnx.raw('SHOW statement_timeout') + const timeout = result.rows[0].statement_timeout + + // Should be set to 30s (default) or whatever DATABASE_STATEMENT_TIMEOUT is set to + expect(timeout).toBeTruthy() + expect(timeout).not.toBe('0') + + await tnx.commit() + } catch (e) { + await tnx.rollback() + throw e + } finally { + await pool.destroy() + } + }) +}) + +describe('TenantConnection Abort Signal', () => { + it('should store and retrieve abort signal', async () => { + const superUser = await getServiceKeyUser(tenantId) + const poolManager = new PoolManager() + const pool = poolManager.getPool({ + tenantId, + isExternalPool: true, + maxConnections: 2, + dbUrl: databasePoolURL || databaseURL, + user: superUser, + superUser, + }) + + const connection = new TenantConnection(pool, { + tenantId, + isExternalPool: true, + maxConnections: 2, + dbUrl: databasePoolURL || databaseURL, + user: superUser, + superUser, + }) + + // Initially no signal + expect(connection.getAbortSignal()).toBeUndefined() + + // Set signal + const controller = new AbortController() + connection.setAbortSignal(controller.signal) + + // Should retrieve the same signal + expect(connection.getAbortSignal()).toBe(controller.signal) + + await pool.destroy() + }) +}) diff --git a/src/test/queue-mocks.test.ts b/src/test/queue-mocks.test.ts new file mode 100644 index 000000000..27f6b1fd6 --- /dev/null +++ b/src/test/queue-mocks.test.ts @@ -0,0 +1,49 @@ +/** + * Real unit tests for queue event handlers + * Tests real business logic with mocked external dependencies + */ + +// Mock only used external dependencies +vi.mock('axios') +vi.mock('@internal/database', () => ({ + getTenantConfig: vi.fn(), +})) +vi.mock('@internal/database/migrations', () => ({ + runMigrationsOnTenant: vi.fn(), +})) + +import { getTenantConfig } from '@internal/database' +import { runMigrationsOnTenant } from '@internal/database/migrations' +import axios from 'axios' + +const mockAxios = vi.mocked(axios) +const mockGetTenantConfig = vi.mocked(getTenantConfig) +const mockRunMigrationsOnTenant = vi.mocked(runMigrationsOnTenant) + +describe('Error Handling Patterns', () => { + it('should handle network errors gracefully', async () => { + const networkError = new Error('Network error') + mockAxios.post.mockRejectedValue(networkError) + + await expect(mockAxios.post('https://example.com/webhook', {})).rejects.toThrow('Network error') + }) + + it('should handle database errors gracefully', async () => { + const dbError = new Error('Database connection failed') + mockGetTenantConfig.mockRejectedValue(dbError) + + await expect(mockGetTenantConfig('test-tenant')).rejects.toThrow('Database connection failed') + }) + + it('should handle migration errors gracefully', async () => { + const migrationError = new Error('Migration failed') + mockRunMigrationsOnTenant.mockRejectedValue(migrationError) + + await expect( + mockRunMigrationsOnTenant({ + databaseUrl: 'postgres://test:test@localhost:5432/test', + tenantId: 'test-tenant', + }) + ).rejects.toThrow('Migration failed') + }) +}) diff --git a/src/test/render-routes.test.ts b/src/test/render-routes.test.ts index 358f9dcf0..76ede90ea 100644 --- a/src/test/render-routes.test.ts +++ b/src/test/render-routes.test.ts @@ -1,14 +1,14 @@ +import { generateHS512JWK, SignedToken, signJWT, verifyJWT } from '@internal/auth' +import axios from 'axios' import dotenv from 'dotenv' +import { FastifyInstance } from 'fastify' import fs from 'fs/promises' -import { getConfig, JwksConfig, mergeConfig } from '../config' +import path from 'path' import app from '../app' +import { getConfig, JwksConfig, mergeConfig } from '../config' import { S3Backend } from '../storage/backend' -import path from 'path' import { ImageRenderer } from '../storage/renderer' -import axios from 'axios' import { useMockObject } from './common' -import { generateHS512JWK, SignedToken, signJWT, verifyJWT } from '@internal/auth' -import { FastifyInstance } from 'fastify' dotenv.config({ path: '.env.test' }) const { imgProxyURL, jwtSecret } = getConfig() @@ -34,13 +34,13 @@ describe('image rendering routes', () => { afterEach(async () => { await appInstance.close() - jest.clearAllMocks() + vi.clearAllMocks() }) it('will render an authenticated image applying transformations using external image processing', async () => { const testAxios = axios.create({ baseURL: imgProxyURL }) - jest.spyOn(ImageRenderer.prototype, 'getClient').mockReturnValue(testAxios) - const axiosSpy = jest.spyOn(testAxios, 'get') + vi.spyOn(ImageRenderer.prototype, 'getClient').mockReturnValue(testAxios) + const axiosSpy = vi.spyOn(testAxios, 'get') const response = await appInstance.inject({ method: 'GET', @@ -51,8 +51,8 @@ describe('image rendering routes', () => { }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.privateAssetUrl).toBeCalledTimes(1) - expect(axiosSpy).toBeCalledWith( + expect(S3Backend.prototype.privateAssetUrl).toHaveBeenCalledTimes(1) + expect(axiosSpy).toHaveBeenCalledWith( `/public/height:100/width:100/resizing_type:fill/plain/local:///${projectRoot}/data/sadcat.jpg`, { responseType: 'stream', signal: expect.any(AbortSignal) } ) @@ -60,8 +60,8 @@ describe('image rendering routes', () => { it('will render a public image applying transformations using external image processing', async () => { const testAxios = axios.create({ baseURL: imgProxyURL }) - jest.spyOn(ImageRenderer.prototype, 'getClient').mockReturnValue(testAxios) - const axiosSpy = jest.spyOn(testAxios, 'get') + vi.spyOn(ImageRenderer.prototype, 'getClient').mockReturnValue(testAxios) + const axiosSpy = vi.spyOn(testAxios, 'get') const response = await appInstance.inject({ method: 'GET', @@ -69,13 +69,36 @@ describe('image rendering routes', () => { }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.privateAssetUrl).toBeCalledTimes(1) - expect(axiosSpy).toBeCalledWith( + expect(S3Backend.prototype.privateAssetUrl).toHaveBeenCalledTimes(1) + expect(axiosSpy).toHaveBeenCalledWith( `/public/height:100/width:100/resizing_type:fill/plain/local:///${projectRoot}/data/sadcat.jpg`, { responseType: 'stream', signal: expect.any(AbortSignal) } ) }) + it('will render a public image in all supported formats', async () => { + const formats = ['origin', 'webp', 'avif'] + const testAxios = axios.create({ baseURL: imgProxyURL }) + vi.spyOn(ImageRenderer.prototype, 'getClient').mockReturnValue(testAxios) + const axiosSpy = vi.spyOn(testAxios, 'get') + + for (let format of formats) { + const response = await appInstance.inject({ + method: 'GET', + url: `/render/image/public/public-bucket-2/favicon.ico?format=${format}&width=100&height=100`, + }) + + expect(response.statusCode).toBe(200) + expect(S3Backend.prototype.privateAssetUrl).toHaveBeenCalledTimes(1) + const expectFormat = format === 'origin' ? '' : `/format:${format}` + expect(axiosSpy).toHaveBeenCalledWith( + `/public/height:100/width:100/resizing_type:fill${expectFormat}/plain/local:///${projectRoot}/data/sadcat.jpg`, + { responseType: 'stream', signal: expect.any(AbortSignal) } + ) + vi.clearAllMocks() + } + }) + it('will render a transformed image providing a signed url', async () => { const assetUrl = 'bucket2/authenticated/casestudy.png' const signURLResponse = await appInstance.inject({ @@ -103,8 +126,8 @@ describe('image rendering routes', () => { expect(jwtData.url).toBe(assetUrl) const testAxios = axios.create({ baseURL: imgProxyURL }) - jest.spyOn(ImageRenderer.prototype, 'getClient').mockReturnValue(testAxios) - const axiosSpy = jest.spyOn(testAxios, 'get') + vi.spyOn(ImageRenderer.prototype, 'getClient').mockReturnValue(testAxios) + const axiosSpy = vi.spyOn(testAxios, 'get') const response = await appInstance.inject({ method: 'GET', @@ -112,8 +135,8 @@ describe('image rendering routes', () => { }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.privateAssetUrl).toBeCalledTimes(1) - expect(axiosSpy).toBeCalledWith( + expect(S3Backend.prototype.privateAssetUrl).toHaveBeenCalledTimes(1) + expect(axiosSpy).toHaveBeenCalledWith( `/public/height:100/width:100/resizing_type:fit/plain/local:///${projectRoot}/data/sadcat.jpg`, { responseType: 'stream', signal: expect.any(AbortSignal) } ) @@ -150,8 +173,8 @@ describe('image rendering routes', () => { expect(jwtData.url).toBe(assetUrl) const testAxios = axios.create({ baseURL: imgProxyURL }) - jest.spyOn(ImageRenderer.prototype, 'getClient').mockReturnValue(testAxios) - const axiosSpy = jest.spyOn(testAxios, 'get') + vi.spyOn(ImageRenderer.prototype, 'getClient').mockReturnValue(testAxios) + const axiosSpy = vi.spyOn(testAxios, 'get') const response = await appInstance.inject({ method: 'GET', @@ -159,8 +182,8 @@ describe('image rendering routes', () => { }) expect(response.statusCode).toBe(200) - expect(S3Backend.prototype.privateAssetUrl).toBeCalledTimes(1) - expect(axiosSpy).toBeCalledWith( + expect(S3Backend.prototype.privateAssetUrl).toHaveBeenCalledTimes(1) + expect(axiosSpy).toHaveBeenCalledWith( `/public/height:100/width:100/resizing_type:fit/plain/local:///${projectRoot}/data/sadcat.jpg`, { responseType: 'stream', signal: expect.any(AbortSignal) } ) @@ -194,4 +217,36 @@ describe('image rendering routes', () => { const body = response.json<{ error: string }>() expect(body.error).toBe('InvalidSignature') }) + + describe('transformation parameter validation', () => { + it('rejects format parameter with newline character in info route', async () => { + const response = await appInstance.inject({ + method: 'GET', + url: '/object/info/public/public-bucket-2/favicon.ico?format=avif%0Amalicious', + }) + + expect(response.statusCode).toBe(400) + const body = response.json<{ error: string; message: string }>() + expect(body.message).toContain('format') + expect(body.message).toContain('must be equal to one of the allowed values') + }) + + it('rejects resize parameter with newline character in HEAD route', async () => { + const response = await appInstance.inject({ + method: 'HEAD', + url: '/object/public/public-bucket-2/favicon.ico?resize=cover%0Amalicious', + }) + + expect(response.statusCode).toBe(400) + }) + + it('accepts valid transformation parameters in info route', async () => { + const response = await appInstance.inject({ + method: 'GET', + url: '/object/info/public/public-bucket-2/favicon.ico?width=100&height=200', + }) + + expect(response.statusCode).toBe(200) + }) + }) }) diff --git a/src/test/rls.test.ts b/src/test/rls.test.ts index f6084ee46..3f717a2e3 100644 --- a/src/test/rls.test.ts +++ b/src/test/rls.test.ts @@ -1,25 +1,30 @@ +import { + CompleteMultipartUploadCommand, + CreateBucketCommand, + CreateMultipartUploadCommand, + S3Client, + S3ServiceException, + UploadPartCommand, +} from '@aws-sdk/client-s3' +import { signJWT } from '@internal/auth' +import { wait } from '@internal/concurrency' +import { getPostgresConnection, getServiceKeyUser } from '@internal/database' +import { createStorageBackend } from '@storage/backend' +import { StorageKnexDB } from '@storage/database' +import { TenantLocation } from '@storage/locator' import { randomUUID } from 'crypto' -import { Knex, knex } from 'knex' -import fs from 'fs' -import path from 'path' +import { FastifyInstance } from 'fastify' import FormData from 'form-data' +import fs from 'fs' import yaml from 'js-yaml' -import Mustache from 'mustache' -import { CreateBucketCommand, S3Client } from '@aws-sdk/client-s3' - -import { StorageKnexDB } from '@storage/database' -import { createStorageBackend } from '@storage/backend' -import { getPostgresConnection } from '@internal/database' -import { getServiceKeyUser } from '@internal/database' -import { signJWT } from '@internal/auth' - +import { Knex, knex } from 'knex' +import path from 'path' +import * as tus from 'tus-js-client' +import { DetailedError } from 'tus-js-client' import app from '../app' import { getConfig } from '../config' -import { checkBucketExists } from './common' import { Storage } from '../storage' -import { FastifyInstance } from 'fastify' -import { wait } from '@internal/concurrency' -import { TenantLocation } from '@storage/locator' +import { checkBucketExists } from './common' interface Policy { name: string @@ -42,7 +47,11 @@ interface TestCaseAssert { operation: | 'upload' | 'upload.upsert' + | 'upload.tus' + | 'upload.signed' + | 'upload.s3.multipart' | 'bucket.create' + | 'bucket.empty' | 'bucket.get' | 'bucket.list' | 'bucket.delete' @@ -50,6 +59,7 @@ interface TestCaseAssert { | 'object.delete' | 'object.get' | 'object.list' + | 'object.seed' | 'object.move' | 'object.copy' @@ -58,6 +68,11 @@ interface TestCaseAssert { useExistingBucketName?: string role?: string policies?: string[] + userMetadata?: Record + mimeType?: string + contentLength?: number + copyMetadata?: boolean + destinationObjectName?: string status: number error?: string } @@ -67,17 +82,41 @@ interface RlsTestSpec { tests: TestCase[] } +function renderRlsTemplate(template: string, values: Record) { + const rendered = template.replace(/{{\s*([a-zA-Z0-9_]+)\s*}}/g, (_match, key: string) => { + if (!Object.prototype.hasOwnProperty.call(values, key)) { + throw new Error(`Unknown RLS template variable: ${key}`) + } + + return values[key] + }) + + if (rendered.includes('{{')) { + throw new Error('Unsupported template tag in rls_tests.yaml') + } + + return rendered +} + const testSpec = yaml.load( fs.readFileSync(path.resolve(__dirname, 'rls_tests.yaml'), 'utf8') ) as RlsTestSpec -const { serviceKeyAsync, tenantId, jwtSecret, databaseURL, storageS3Bucket, storageBackendType } = - getConfig() +const { + serviceKeyAsync, + anonKeyAsync, + tenantId, + jwtSecret, + databaseURL, + storageS3Bucket, + storageBackendType, + storageS3Region, +} = getConfig() const backend = createStorageBackend(storageBackendType) const client = backend.client let appInstance: FastifyInstance - -jest.setTimeout(10000) +let currentUserId: string +let currentStorage: Storage describe('RLS policies', () => { let db: Knex @@ -101,20 +140,18 @@ describe('RLS policies', () => { }) }) - let userId: string let jwt: string - let storage: Storage beforeEach(async () => { appInstance = app() - userId = randomUUID() - jwt = (await signJWT({ sub: userId, role: 'authenticated' }, jwtSecret, '1h')) as string + currentUserId = randomUUID() + jwt = (await signJWT({ sub: currentUserId, role: 'authenticated' }, jwtSecret, '1h')) as string await db.table('auth.users').insert({ instance_id: '00000000-0000-0000-0000-000000000000', - id: userId, + id: currentUserId, aud: 'authenticated', role: 'authenticated', - email: userId + '@supabase.io', + email: currentUserId + '@supabase.io', }) const adminUser = await getServiceKeyUser(tenantId) @@ -131,17 +168,17 @@ describe('RLS policies', () => { tenantId, }) - storage = new Storage(backend, knexDB, new TenantLocation(storageS3Bucket)) + currentStorage = new Storage(backend, knexDB, new TenantLocation(storageS3Bucket)) }) afterEach(async () => { await appInstance.close() - jest.clearAllMocks() + vi.clearAllMocks() }) afterAll(async () => { await db.destroy() - await (storage.db as StorageKnexDB).connection.dispose() + await (currentStorage.db as StorageKnexDB).connection.dispose() }) testSpec.tests.forEach((_test, index) => { @@ -154,8 +191,8 @@ describe('RLS policies', () => { const originalBucketName = bucketName const testScopedSpec = yaml.load( - Mustache.render(content, { - uid: userId, + renderRlsTemplate(content, { + uid: currentUserId, bucketName, objectName, runId, @@ -180,11 +217,11 @@ describe('RLS policies', () => { // Prepare if (test.setup?.create_bucket !== false) { - await storage.createBucket({ + await currentStorage.createBucket({ name: bucketName, id: bucketName, public: false, - owner: userId, + owner: currentUserId, }) console.log(`Created bucket ${bucketName}`) } @@ -194,11 +231,11 @@ describe('RLS policies', () => { for (const assert of test.asserts) { if (assert.bucketName) { bucketName = assert.bucketName - await storage.createBucket({ + await currentStorage.createBucket({ name: bucketName, id: bucketName, public: false, - owner: userId, + owner: currentUserId, }) console.log(`Created bucket ${bucketName}`) } @@ -234,8 +271,13 @@ describe('RLS policies', () => { try { const response = await runOperation(assert.operation, { bucket: bucketName, - objectName: objectName, + objectName, jwt: assert.role === 'service' ? await serviceKeyAsync : jwt, + userMetadata: assert.userMetadata, + mimeType: assert.mimeType, + contentLength: assert.contentLength, + copyMetadata: assert.copyMetadata, + destinationObjectName: assert.destinationObjectName, }) console.log( @@ -254,8 +296,7 @@ describe('RLS policies', () => { } if (assert.error) { - const body = await response.json() - + const body = response.json() expect(body.message).toBe(assert.error) } } finally { @@ -279,10 +320,13 @@ describe('RLS policies', () => { throw e } finally { await wait(2000) - const policiesToDelete = allPolicies.reduce((acc, policy) => { - acc.push(...policy) - return acc - }, [] as { name: string; table: string }[]) + const policiesToDelete = allPolicies.reduce( + (acc, policy) => { + acc.push(...policy) + return acc + }, + [] as { name: string; table: string }[] + ) for (const policy of policiesToDelete) { await db.raw(`DROP POLICY IF EXISTS "${policy.name}" ON ${policy.table};`) @@ -294,15 +338,39 @@ describe('RLS policies', () => { async function runOperation( operation: TestCaseAssert['operation'], - options: { bucket: string; jwt: string; objectName: string } + options: { + bucket: string + jwt: string + objectName: string + userMetadata?: Record + mimeType?: string + contentLength?: number + copyMetadata?: boolean + destinationObjectName?: string + } ) { - const { jwt, bucket, objectName } = options + const { + jwt, + bucket, + objectName, + userMetadata, + mimeType, + contentLength, + copyMetadata, + destinationObjectName, + } = options switch (operation) { case 'upload': - return uploadFile(bucket, objectName, jwt) + return uploadFile(bucket, objectName, jwt, false, userMetadata, mimeType, contentLength) case 'upload.upsert': - return uploadFile(bucket, objectName, jwt, true) + return uploadFile(bucket, objectName, jwt, true, userMetadata, mimeType, contentLength) + case 'upload.tus': + return tusUploadFile(bucket, objectName, jwt, userMetadata, mimeType, contentLength) + case 'upload.signed': + return signUploadUrl(bucket, objectName, jwt, userMetadata) + case 'upload.s3.multipart': + return s3MultipartUpload(bucket, objectName, jwt, userMetadata) case 'bucket.list': return appInstance.inject({ method: 'GET', @@ -330,6 +398,14 @@ async function runOperation( name: bucket, }, }) + case 'bucket.empty': + return appInstance.inject({ + method: 'POST', + url: `/bucket/${bucket}/empty`, + headers: { + authorization: `Bearer ${jwt}`, + }, + }) case 'bucket.update': console.log(`updating bucket ${bucket}`) return appInstance.inject({ @@ -381,6 +457,17 @@ async function runOperation( }, }, }) + case 'object.seed': + await currentStorage.db.createObject({ + bucket_id: bucket, + name: objectName, + owner: currentUserId, + metadata: { size: 1 }, + user_metadata: null, + version: randomUUID(), + }) + + return { statusCode: 200, body: '{}', json: () => ({}) } case 'object.move': return appInstance.inject({ method: 'POST', @@ -400,11 +487,15 @@ async function runOperation( url: `/object/copy`, headers: { authorization: `Bearer ${jwt}`, + ...(userMetadata + ? { 'x-metadata': Buffer.from(JSON.stringify(userMetadata)).toString('base64') } + : {}), }, payload: { bucketId: bucket, sourceKey: objectName, - destinationKey: 'copied_' + objectName, + destinationKey: destinationObjectName ?? 'copied_' + objectName, + copyMetadata: copyMetadata ?? true, }, }) default: @@ -454,13 +545,31 @@ async function createPolicy(db: Knex, policy: Policy) { return Promise.all(created) } -async function uploadFile(bucket: string, fileName: string, jwt: string, upsert?: boolean) { +async function uploadFile( + bucket: string, + fileName: string, + jwt: string, + upsert?: boolean, + userMetadata?: Record, + mimeType?: string, + contentLength?: number +) { const testFile = fs.createReadStream(path.resolve(__dirname, 'assets', 'sadcat.jpg')) const form = new FormData() form.append('file', testFile) + + if (userMetadata) { + form.append('metadata', JSON.stringify(userMetadata)) + } + + if (mimeType) { + form.append('contentType', mimeType) + } + const headers = Object.assign({}, form.getHeaders(), { authorization: `Bearer ${jwt}`, ...(upsert ? { 'x-upsert': 'true' } : {}), + ...(contentLength ? { 'content-length': contentLength.toString() } : {}), }) return appInstance.inject({ @@ -470,3 +579,158 @@ async function uploadFile(bucket: string, fileName: string, jwt: string, upsert? payload: form, }) } + +async function tusUploadFile( + bucket: string, + objectName: string, + jwt: string, + userMetadata?: Record, + mimeType?: string, + contentLength?: number +) { + if (!appInstance.server.listening) { + await appInstance.listen({ port: 0 }) + } + + const addressInfo = appInstance.server.address() + if (!addressInfo || typeof addressInfo === 'string') { + throw new Error('Unable to resolve local server address') + } + + const localServerAddress = `http://127.0.0.1:${addressInfo.port}` + + const file = fs.createReadStream(path.resolve(__dirname, 'assets', 'sadcat.jpg')) + + let statusCode = 200 + let message = '' + + try { + await new Promise((resolve, reject) => { + const upload = new tus.Upload(file, { + endpoint: `${localServerAddress}/upload/resumable`, + uploadSize: contentLength || undefined, + onShouldRetry: () => false, + uploadDataDuringCreation: false, + headers: { + authorization: `Bearer ${jwt}`, + }, + metadata: { + bucketName: bucket, + objectName, + contentType: mimeType || 'application/octet-stream', + cacheControl: '3600', + ...(userMetadata ? { metadata: JSON.stringify(userMetadata) } : {}), + }, + onError(error) { + console.log('Failed because: ' + error) + reject(error) + }, + onSuccess: () => { + resolve(true) + }, + }) + + upload.start() + }) + } catch (e) { + if (!(e instanceof DetailedError)) throw e + + statusCode = e.originalResponse.getStatus() + message = e.originalResponse.getBody() + } + + const body = message ? { message } : {} + return { statusCode, body: JSON.stringify(body), json: () => body } +} + +async function signUploadUrl( + bucket: string, + objectName: string, + jwt: string, + userMetadata?: Record +) { + const metadata = userMetadata + ? Buffer.from(JSON.stringify(userMetadata)).toString('base64') + : undefined + + return appInstance.inject({ + method: 'POST', + url: `/object/upload/sign/${bucket}/${objectName}`, + headers: { + authorization: `Bearer ${jwt}`, + ...(metadata ? { 'x-metadata': metadata } : {}), + }, + }) +} + +async function s3MultipartUpload( + bucket: string, + objectName: string, + jwt: string, + userMetadata?: Record +) { + if (!appInstance.server.listening) { + await appInstance.listen({ port: 0 }) + } + + const listener = appInstance.server.address() as { port: number } + const anonKey = await anonKeyAsync + const s3Client = new S3Client({ + endpoint: `http://127.0.0.1:${listener.port}/s3`, + forcePathStyle: true, + region: storageS3Region, + credentials: { + accessKeyId: tenantId, + secretAccessKey: anonKey, + sessionToken: jwt, + }, + }) + + let statusCode = 200 + let message = '' + + try { + const s3Metadata = userMetadata + ? Object.fromEntries(Object.entries(userMetadata).map(([k, v]) => [k, String(v)])) + : undefined + + const createResp = await s3Client.send( + new CreateMultipartUploadCommand({ + Bucket: bucket, + Key: objectName, + ContentType: 'image/jpg', + ...(s3Metadata ? { Metadata: s3Metadata } : {}), + }) + ) + + const data = Buffer.alloc(5 * 1024) + const partResp = await s3Client.send( + new UploadPartCommand({ + Bucket: bucket, + Key: objectName, + UploadId: createResp.UploadId, + PartNumber: 1, + Body: data, + ContentLength: data.length, + }) + ) + + await s3Client.send( + new CompleteMultipartUploadCommand({ + Bucket: bucket, + Key: objectName, + UploadId: createResp.UploadId, + MultipartUpload: { Parts: [{ PartNumber: 1, ETag: partResp.ETag }] }, + }) + ) + } catch (e: unknown) { + if (!(e instanceof S3ServiceException)) throw e + + statusCode = e.$metadata.httpStatusCode ?? 400 + message = e.message + } finally { + s3Client.destroy() + } + const body = message ? { message } : {} + return { statusCode, body: JSON.stringify(body), json: () => body } +} diff --git a/src/test/rls_tests.yaml b/src/test/rls_tests.yaml index 150c43a70..4b8649e9c 100644 --- a/src/test/rls_tests.yaml +++ b/src/test/rls_tests.yaml @@ -1,80 +1,110 @@ policies: - name: read_only_all_buckets - tables: ['storage.buckets'] - roles: ['authenticated'] - permissions: ['select'] + tables: ["storage.buckets"] + roles: ["authenticated"] + permissions: ["select"] content: "USING(owner = '{{uid}}')" - name: read_only_all_objects - tables: ['storage.objects'] - roles: ['authenticated'] - permissions: ['select'] + tables: ["storage.objects"] + roles: ["authenticated"] + permissions: ["select"] content: "USING(owner = '{{uid}}')" - name: insert_only_all_objects - tables: ['storage.objects'] - roles: ['authenticated'] - permissions: ['insert'] + tables: ["storage.objects"] + roles: ["authenticated"] + permissions: ["insert"] content: "WITH CHECK(auth.uid() = '{{uid}}')" - name: insert_only_all_buckets - tables: ['storage.buckets'] - roles: ['authenticated'] - permissions: ['insert'] + tables: ["storage.buckets"] + roles: ["authenticated"] + permissions: ["insert"] content: "WITH CHECK(auth.uid() = '{{uid}}')" - name: update_only_all_objects - tables: ['storage.objects'] - roles: ['authenticated'] - permissions: ['update'] + tables: ["storage.objects"] + roles: ["authenticated"] + permissions: ["update"] content: "USING(owner = '{{uid}}')" - name: update_only_all_buckets - tables: ['storage.buckets'] - roles: ['authenticated'] - permissions: ['update'] + tables: ["storage.buckets"] + roles: ["authenticated"] + permissions: ["update"] content: "USING(owner = '{{uid}}')" - name: delete_only_all_buckets - tables: ['storage.buckets'] - roles: ['authenticated'] - permissions: ['delete'] + tables: ["storage.buckets"] + roles: ["authenticated"] + permissions: ["delete"] content: "USING(owner = '{{uid}}')" - name: delete_only_all_objects - tables: ['storage.objects'] - roles: ['authenticated'] - permissions: ['delete'] + tables: ["storage.objects"] + roles: ["authenticated"] + permissions: ["delete"] content: "USING(owner = '{{uid}}')" + - name: insert_with_metadata_check + tables: ["storage.objects"] + roles: ["authenticated"] + permissions: ["insert"] + content: "WITH CHECK(user_metadata->>'department' = 'engineering')" + + - name: insert_only_images + tables: ["storage.objects"] + roles: ["authenticated"] + permissions: ["insert"] + content: "WITH CHECK(metadata->>'mimetype' LIKE 'image/%')" + + - name: insert_max_size_limit + tables: ["storage.objects"] + roles: ["authenticated"] + permissions: ["insert"] + content: "WITH CHECK((metadata->>'contentLength')::int <= 100000)" + + - name: read_only_list_objects + tables: ["storage.objects"] + roles: ["authenticated"] + permissions: ["select"] + content: "USING(storage.allow_only_operation('object.list') AND owner = '{{uid}}')" + + - name: read_list_or_get_objects + tables: ["storage.objects"] + roles: ["authenticated"] + permissions: ["select"] + content: "USING(storage.allow_any_operation(ARRAY['object.list', 'storage.object.get_authenticated']) AND owner = '{{uid}}')" + tests: - - description: 'Will only able to read objects' + - description: "Will only able to read objects" policies: - read_only_all_objects asserts: - operation: upload status: 400 - error: 'new row violates row-level security policy' + error: "new row violates row-level security policy" - operation: upload.upsert status: 400 - message: 'new row violates row-level security policy' + message: "new row violates row-level security policy" - operation: bucket.create status: 400 - error: 'new row violates row-level security policy' + error: "new row violates row-level security policy" - operation: bucket.delete status: 400 - error: 'Bucket not found' + error: "Bucket not found" - operation: bucket.update status: 400 - error: 'Bucket not found' + error: "Bucket not found" - operation: bucket.get status: 400 - error: 'Bucket not found' + error: "Bucket not found" - operation: upload status: 200 @@ -88,48 +118,48 @@ tests: - operation: object.delete role: authenticated status: 400 - message: 'Object not found' + message: "Object not found" - operation: upload - bucketName: 'bucket_{{runId}}' - objectName: 'object_{{runId}}.txt' + bucketName: "bucket_{{runId}}" + objectName: "object_{{runId}}.txt" policies: - insert_only_all_objects status: 200 - operation: object.copy - useExistingBucketName: 'bucket_{{runId}}' - objectName: 'object_{{runId}}.txt' + useExistingBucketName: "bucket_{{runId}}" + objectName: "object_{{runId}}.txt" status: 400 - operation: object.move - useExistingBucketName: 'bucket_{{runId}}' - objectName: 'object_{{runId}}.txt' + useExistingBucketName: "bucket_{{runId}}" + objectName: "object_{{runId}}.txt" status: 400 - - description: 'Will only able to read buckets' + - description: "Will only able to read buckets" policies: - read_only_all_buckets asserts: - operation: upload status: 400 - error: 'new row violates row-level security policy' + error: "new row violates row-level security policy" - operation: upload.upsert status: 400 - message: 'new row violates row-level security policy' + message: "new row violates row-level security policy" - operation: bucket.create status: 400 - error: 'new row violates row-level security policy' + error: "new row violates row-level security policy" - operation: bucket.delete status: 400 - error: 'Bucket not found' + error: "Bucket not found" - operation: bucket.update status: 400 - error: 'Bucket not found' + error: "Bucket not found" - operation: bucket.get status: 200 @@ -145,14 +175,26 @@ tests: - operation: object.get role: authenticated status: 400 - message: 'Object not found' + message: "Object not found" - operation: object.delete role: authenticated status: 400 - message: 'Object not found' + message: "Object not found" + + - description: "Will not empty a bucket without delete permission on objects" + policies: + - read_only_all_buckets + - read_only_all_objects + asserts: + - operation: object.seed + status: 200 + + - operation: bucket.empty + status: 400 + error: "Object not found" - - description: 'Will only able to insert objects when authenticated' + - description: "Will only able to insert objects when authenticated" policies: - insert_only_all_objects asserts: @@ -161,18 +203,18 @@ tests: - operation: bucket.delete status: 400 - error: 'Bucket not found' + error: "Bucket not found" - operation: bucket.update status: 400 - error: 'Bucket not found' + error: "Bucket not found" - operation: upload status: 200 - operation: upload.upsert status: 400 - message: 'Object not found' + message: "Object not found" - operation: object.get status: 400 @@ -180,7 +222,7 @@ tests: - operation: object.delete status: 400 - - description: 'Will only able to create buckets when authenticated' + - description: "Will only able to create buckets when authenticated" policies: - insert_only_all_buckets setup: @@ -191,19 +233,19 @@ tests: - operation: bucket.delete status: 400 - error: 'Bucket not found' + error: "Bucket not found" - operation: bucket.update status: 400 - error: 'Bucket not found' + error: "Bucket not found" - operation: upload status: 400 - error: 'new row violates row-level security policy' + error: "new row violates row-level security policy" - operation: upload.upsert status: 400 - message: 'new row violates row-level security policy' + message: "new row violates row-level security policy" - operation: upload policies: @@ -217,42 +259,42 @@ tests: status: 400 - operation: upload - bucketName: 'bucket_{{runId}}' - objectName: 'object_{{runId}}.txt' + bucketName: "bucket_{{runId}}" + objectName: "object_{{runId}}.txt" policies: - insert_only_all_objects status: 200 - operation: object.copy - useExistingBucketName: 'bucket_{{runId}}' - objectName: 'object_{{runId}}.txt' + useExistingBucketName: "bucket_{{runId}}" + objectName: "object_{{runId}}.txt" status: 400 - operation: object.move - useExistingBucketName: 'bucket_{{runId}}' - objectName: 'object_{{runId}}.txt' + useExistingBucketName: "bucket_{{runId}}" + objectName: "object_{{runId}}.txt" status: 400 - - description: 'Will only able to update buckets when authenticated' + - description: "Will only able to update buckets when authenticated" policies: - update_only_all_buckets - read_only_all_buckets asserts: - operation: bucket.create status: 400 - error: 'new row violates row-level security policy' + error: "new row violates row-level security policy" - operation: bucket.delete status: 400 - error: 'Bucket not found' + error: "Bucket not found" - operation: upload status: 400 - error: 'new row violates row-level security policy' + error: "new row violates row-level security policy" - operation: upload.upsert status: 400 - message: 'new row violates row-level security policy' + message: "new row violates row-level security policy" - operation: upload policies: @@ -261,11 +303,11 @@ tests: - operation: object.get status: 400 - error: 'Object not found' + error: "Object not found" - operation: object.delete status: 400 - error: 'Object not found' + error: "Object not found" - operation: bucket.update status: 200 @@ -273,7 +315,7 @@ tests: - operation: bucket.get status: 200 - - description: 'Will only able to update objects when authenticated' + - description: "Will only able to update objects when authenticated" policies: - insert_only_all_objects - update_only_all_objects @@ -281,20 +323,20 @@ tests: asserts: - operation: bucket.create status: 400 - error: 'new row violates row-level security policy' + error: "new row violates row-level security policy" - operation: bucket.delete status: 400 - error: 'Bucket not found' + error: "Bucket not found" - operation: upload - bucketName: 'bucket_{{runId}}' - objectName: 'to_upsert.txt' + bucketName: "bucket_{{runId}}" + objectName: "to_upsert.txt" status: 200 - operation: upload.upsert - existingBucketName: 'bucket_{{runId}}' - objectName: 'to_upsert.txt' + existingBucketName: "bucket_{{runId}}" + objectName: "to_upsert.txt" status: 200 - operation: object.get @@ -302,17 +344,17 @@ tests: - operation: object.delete status: 400 - error: 'Object not found' + error: "Object not found" - operation: bucket.update status: 400 - message: 'Bucket not found' + message: "Bucket not found" - operation: bucket.get status: 400 - message: 'Bucket not found' + message: "Bucket not found" - - description: 'Will only able to delete buckets when authenticated' + - description: "Will only able to delete buckets when authenticated" policies: - delete_only_all_buckets - read_only_all_buckets @@ -322,15 +364,15 @@ tests: - operation: bucket.create status: 400 - error: 'new row violates row-level security policy' + error: "new row violates row-level security policy" - operation: upload status: 400 - error: 'new row violates row-level security policy' + error: "new row violates row-level security policy" - operation: upload.upsert status: 400 - message: 'new row violates row-level security policy' + message: "new row violates row-level security policy" - operation: upload policies: @@ -339,11 +381,11 @@ tests: - operation: object.get status: 400 - error: 'Object not found' + error: "Object not found" - operation: object.delete status: 400 - error: 'Object not found' + error: "Object not found" - operation: object.delete role: service @@ -352,80 +394,80 @@ tests: - operation: bucket.delete status: 200 # - - description: 'Will only able to delete objects when authenticated' + - description: "Will only able to delete objects when authenticated" policies: - delete_only_all_objects - read_only_all_objects asserts: - operation: bucket.get status: 400 - error: 'Bucket not found' + error: "Bucket not found" - operation: bucket.create status: 400 - error: 'new row violates row-level security policy' + error: "new row violates row-level security policy" - operation: upload status: 400 - error: 'new row violates row-level security policy' + error: "new row violates row-level security policy" - operation: upload.upsert status: 400 - message: 'new row violates row-level security policy' + message: "new row violates row-level security policy" - operation: upload - bucketName: 'bucket_delete_test_{{runId}}' - objectName: 'object_{{runId}}' + bucketName: "bucket_delete_test_{{runId}}" + objectName: "object_{{runId}}" policies: - insert_only_all_objects status: 200 - operation: object.delete - useExistingBucketName: 'bucket_delete_test_{{runId}}' - objectName: 'object_{{runId}}' + useExistingBucketName: "bucket_delete_test_{{runId}}" + objectName: "object_{{runId}}" status: 200 - operation: object.get - useExistingBucketName: 'bucket_delete_test_{{runId}}' - objectName: 'object_{{runId}}' + useExistingBucketName: "bucket_delete_test_{{runId}}" + objectName: "object_{{runId}}" status: 400 - error: 'Object not found' + error: "Object not found" - operation: bucket.delete status: 400 - error: 'Bucket not found' + error: "Bucket not found" - - description: 'Will only able to move objects when authenticated' + - description: "Will only able to move objects when authenticated" policies: - read_only_all_objects - update_only_all_objects asserts: - operation: bucket.get status: 400 - error: 'Bucket not found' + error: "Bucket not found" - operation: bucket.create status: 400 - error: 'new row violates row-level security policy' + error: "new row violates row-level security policy" - operation: upload status: 400 - error: 'new row violates row-level security policy' + error: "new row violates row-level security policy" - operation: upload.upsert status: 400 - message: 'new row violates row-level security policy' + message: "new row violates row-level security policy" - operation: upload - bucketName: 'bucket_to_move_{{runId}}' - objectName: 'object_to_move_{{runId}}.txt' + bucketName: "bucket_to_move_{{runId}}" + objectName: "object_to_move_{{runId}}.txt" policies: - insert_only_all_objects status: 200 - operation: object.move - useExistingBucketName: 'bucket_to_move_{{runId}}' - objectName: 'object_to_move_{{runId}}.txt' + useExistingBucketName: "bucket_to_move_{{runId}}" + objectName: "object_to_move_{{runId}}.txt" status: 200 - operation: object.delete @@ -433,40 +475,40 @@ tests: - operation: bucket.delete status: 400 - error: 'Bucket not found' + error: "Bucket not found" - - description: 'Will only able to copy owned objects when authenticated' + - description: "Will only able to copy owned objects when authenticated" policies: - read_only_all_objects - insert_only_all_objects asserts: - operation: bucket.get status: 400 - error: 'Bucket not found' + error: "Bucket not found" - operation: bucket.create status: 400 - error: 'new row violates row-level security policy' + error: "new row violates row-level security policy" - operation: upload - bucketName: 'bucket_{{runId}}' - objectName: 'object_{{runId}}.txt' + bucketName: "bucket_{{runId}}" + objectName: "object_{{runId}}.txt" status: 200 - operation: upload.upsert - useExistingBucketName: 'bucket_{{runId}}' - objectName: 'object_{{runId}}.txt' + useExistingBucketName: "bucket_{{runId}}" + objectName: "object_{{runId}}.txt" status: 400 - message: 'new row violates row-level security policy' + message: "new row violates row-level security policy" - operation: upload - bucketName: 'bucket_to_copy_{{runId}}' - objectName: 'object_to_copy_{{runId}}.txt' + bucketName: "bucket_to_copy_{{runId}}" + objectName: "object_to_copy_{{runId}}.txt" status: 200 - operation: object.copy - useExistingBucketName: 'bucket_to_copy_{{runId}}' - objectName: 'object_to_copy_{{runId}}.txt' + useExistingBucketName: "bucket_to_copy_{{runId}}" + objectName: "object_to_copy_{{runId}}.txt" status: 200 - operation: object.delete @@ -474,4 +516,168 @@ tests: - operation: bucket.delete status: 400 - error: 'Bucket not found' + error: "Bucket not found" + + - description: "Will only upload files with correct user metadata" + policies: + - insert_with_metadata_check + - read_only_all_objects + asserts: + - operation: upload + objectName: "test_file.jpg" + userMetadata: + department: "engineering" + status: 200 + + - operation: upload + status: 400 + error: "new row violates row-level security policy" + + - operation: upload.tus + objectName: "test_file_tus.jpg" + userMetadata: + department: "engineering" + status: 200 + + - operation: upload.tus + status: 403 + error: "new row violates row-level security policy" + + - operation: upload + objectName: "source_copy_meta.jpg" + userMetadata: + department: "engineering" + status: 200 + + - operation: object.copy + objectName: "source_copy_meta.jpg" + destinationObjectName: "copied_source_copy_meta_1.jpg" + copyMetadata: true + status: 200 + + - operation: object.copy + objectName: "source_copy_meta.jpg" + destinationObjectName: "copied_source_copy_meta_2.jpg" + copyMetadata: false + userMetadata: + department: "engineering" + status: 200 + + - operation: object.copy + objectName: "source_copy_meta.jpg" + destinationObjectName: "copied_source_copy_meta_3.jpg" + copyMetadata: false + userMetadata: + department: "marketing" + status: 400 + error: "new row violates row-level security policy" + + - operation: upload.signed + objectName: "signed_file.jpg" + userMetadata: + department: "engineering" + status: 200 + + - operation: upload.signed + objectName: "signed_file_no_meta.jpg" + status: 400 + error: "new row violates row-level security policy" + + - operation: upload.s3.multipart + objectName: "s3_multi_file.jpg" + userMetadata: + department: "engineering" + status: 200 + + - operation: upload.s3.multipart + objectName: "s3_multi_file_no_meta.jpg" + status: 403 + + - description: "Will only upload image files based on mimetype" + policies: + - insert_only_images + asserts: + - operation: upload + objectName: "test_image.jpg" + mimeType: "image/jpeg" + status: 200 + + - operation: upload + objectName: "test_file.txt" + mimeType: "text/plain" + status: 400 + error: "new row violates row-level security policy" + + - operation: upload.tus + objectName: "test_image_tus.jpg" + mimeType: "image/jpeg" + status: 200 + + - operation: upload.tus + objectName: "test_file_tus.txt" + mimeType: "text/plain" + status: 403 + error: "new row violates row-level security policy" + + - description: "Will only upload files under size limit based on contentLength" + policies: + - insert_max_size_limit + asserts: + - operation: upload + objectName: "small_file.jpg" + status: 200 + + - operation: upload + objectName: "large_file.jpg" + contentLength: 200000 + status: 400 + error: "new row violates row-level security policy" + + - operation: upload.tus + objectName: "small_file_tus.jpg" + status: 200 + + - operation: upload.tus + objectName: "large_file_tus.jpg" + contentLength: 200000 + status: 403 + error: "new row violates row-level security policy" + + - description: "Operation helper list-only policies only allow object listing" + policies: + - read_only_list_objects + asserts: + - operation: upload + policies: + - insert_only_all_objects + status: 200 + + - operation: object.list + status: 200 + + - operation: object.get + status: 400 + error: "Object not found" + + - operation: object.delete + status: 400 + error: "Object not found" + + - description: "Operation helper any-of policies allow both list and get" + policies: + - read_list_or_get_objects + asserts: + - operation: upload + policies: + - insert_only_all_objects + status: 200 + + - operation: object.list + status: 200 + + - operation: object.get + status: 200 + + - operation: object.delete + status: 400 + error: "Object not found" diff --git a/src/test/s3-error-code.test.ts b/src/test/s3-error-code.test.ts new file mode 100644 index 000000000..9273e990d --- /dev/null +++ b/src/test/s3-error-code.test.ts @@ -0,0 +1,107 @@ +import { randomUUID } from 'node:crypto' +import { mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { ListPartsCommand, S3Client } from '@aws-sdk/client-s3' +import { KnexMetastore } from '@storage/protocols/iceberg/knex' +import { FastifyInstance } from 'fastify' +import { getConfig } from '../config' +import { useStorage } from './utils/storage' + +const { + icebergBucketDetectionSuffix, + s3ProtocolAccessKeyId, + s3ProtocolAccessKeySecret, + storageS3Region, +} = getConfig() + +async function createFileBackedApp(fileBackendPath: string) { + vi.resetModules() + + const configModule = await import('../config') + + configModule.getConfig({ reload: true }) + configModule.mergeConfig({ + storageBackendType: 'file', + storageFilePath: fileBackendPath, + }) + + return (await import('../app')).default() +} + +describe('S3 protocol error code', () => { + const t = useStorage() + + let testApp: FastifyInstance + let client: S3Client + let icebergMetastore: KnexMetastore + let fileBackendPath: string + + beforeAll(async () => { + fileBackendPath = await mkdtemp(join(tmpdir(), 'storage-file-backend-')) + testApp = await createFileBackedApp(fileBackendPath) + icebergMetastore = new KnexMetastore(t.database.connection.pool.acquire(), { + multiTenant: false, + schema: 'storage', + }) + + const listener = await testApp.listen() + + client = new S3Client({ + endpoint: `${listener.replace('[::1]', 'localhost')}/s3`, + forcePathStyle: true, + region: storageS3Region, + credentials: { + accessKeyId: s3ProtocolAccessKeyId!, + secretAccessKey: s3ProtocolAccessKeySecret!, + }, + }) + }) + + afterAll(async () => { + client?.destroy() + await testApp?.close() + + vi.resetModules() + await rm(fileBackendPath, { recursive: true, force: true }) + }) + + it('returns NotSupported for iceberg list-parts on non-S3 backends', async () => { + const nonce = randomUUID().replaceAll('-', '') + const analyticsBucket = await t.storage.createIcebergBucket({ + name: `ice_bucket_${nonce}`, + }) + const namespaceName = `namespace${nonce}` + const tableName = `table${nonce}` + const internalBucketName = `internal-${nonce}${icebergBucketDetectionSuffix}` + + const namespace = await icebergMetastore.createNamespace({ + name: namespaceName, + bucketName: analyticsBucket.name, + bucketId: analyticsBucket.id, + tenantId: '', + metadata: {}, + }) + + await icebergMetastore.createTable({ + name: tableName, + bucketName: analyticsBucket.name, + bucketId: analyticsBucket.id, + location: `s3://${internalBucketName}`, + namespaceId: namespace.id, + }) + + await expect( + client.send( + new ListPartsCommand({ + Bucket: internalBucketName, + Key: `${namespaceName}/${tableName}/data.parquet`, + UploadId: 'upload-id', + }) + ) + ).rejects.toMatchObject({ + $metadata: expect.objectContaining({ httpStatusCode: 409 }), + name: 'NotSupported', + }) + }) +}) diff --git a/src/test/s3-locker.test.ts b/src/test/s3-locker.test.ts index df8827364..01848190e 100644 --- a/src/test/s3-locker.test.ts +++ b/src/test/s3-locker.test.ts @@ -1,31 +1,51 @@ import dotenv from 'dotenv' import path from 'path' + dotenv.config({ path: path.resolve(__dirname, '..', '..', '.env.test') }) import { - S3Client, CreateBucketCommand, DeleteObjectsCommand, + GetObjectCommand, ListObjectsV2Command, PutObjectCommand, - GetObjectCommand, + S3Client, } from '@aws-sdk/client-s3' +import type { PubSubAdapter } from '@internal/pubsub' +import type { Mock } from 'vitest' import { getConfig } from '../config' -import { S3Locker, S3Lock } from '../storage/protocols/tus/s3-locker' +import { backends } from '../storage' import { LockNotifier } from '../storage/protocols/tus/postgres-locker' +import { S3Lock, S3Locker } from '../storage/protocols/tus/s3-locker' import { checkBucketExists } from './common' -import { backends } from '../storage' const { storageS3Bucket, storageBackendType } = getConfig() const backend = backends.createStorageBackend(storageBackendType) const s3ClientFromBackend = backend.client +type TrackedLock = ReturnType + +function getErrorDetails(error: unknown) { + if (error instanceof Error) { + return error + } + + if (typeof error === 'object' && error !== null) { + return { + name: 'name' in error && typeof error.name === 'string' ? error.name : undefined, + message: 'message' in error && typeof error.message === 'string' ? error.message : undefined, + } + } + + return {} +} + describe('S3Locker', () => { let s3Client: S3Client let locker: S3Locker let testBucket: string let mockNotifier: LockNotifier - let allLocks: Array<{ lock: any; locker: S3Locker }> = [] + let allLocks: TrackedLock[] = [] beforeAll(async () => { // Use the configured S3 client from the backend @@ -48,12 +68,21 @@ describe('S3Locker', () => { await cleanupTestLocks() // Create mock notifier - mockNotifier = { - release: jest.fn(), - onRelease: jest.fn(), - unsubscribe: jest.fn(), - subscribe: jest.fn(), - } as any + const pubSub: PubSubAdapter = { + async start() {}, + async publish() {}, + async subscribe() {}, + async unsubscribe() {}, + async close() {}, + on() { + return this + }, + } + + mockNotifier = new LockNotifier(pubSub) + vi.spyOn(mockNotifier, 'release').mockResolvedValue() + vi.spyOn(mockNotifier, 'onRelease') + vi.spyOn(mockNotifier, 'unsubscribe') // Create fresh locker instance locker = new S3Locker({ @@ -66,19 +95,19 @@ describe('S3Locker', () => { maxRetries: 5, retryDelayMs: 100, logger: { - log: jest.fn(), - warn: jest.fn(), - error: jest.fn(), + log: vi.fn(), + warn: vi.fn(), + error: vi.fn(), }, }) }) afterEach(async () => { // Clean up all tracked locks first - for (const { lock } of allLocks) { + for (const lock of allLocks) { try { await lock.unlock() - } catch (error) { + } catch { // Ignore cleanup errors } } @@ -89,10 +118,10 @@ describe('S3Locker', () => { afterAll(async () => { // Final cleanup - ensure all locks are released - for (const { lock } of allLocks) { + for (const lock of allLocks) { try { await lock.unlock() - } catch (error) { + } catch { // Ignore cleanup errors } } @@ -106,13 +135,13 @@ describe('S3Locker', () => { if (s3Client && typeof s3Client.destroy === 'function') { s3Client.destroy() } - } catch (error) { + } catch { // Ignore cleanup errors } }) - function trackLock(lock: any, lockLocker: S3Locker = locker) { - allLocks.push({ lock, locker: lockLocker }) + function trackLock(lock: TrackedLock) { + allLocks.push(lock) return lock } @@ -135,7 +164,7 @@ describe('S3Locker', () => { }) ) } - } catch (error) { + } catch { // Ignore cleanup errors } } @@ -149,7 +178,7 @@ describe('S3Locker', () => { test('should acquire and release a lock successfully', async () => { const lock = locker.newLock('test-lock-1') const abortController = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() // Should be able to acquire lock await expect(lock.lock(abortController.signal, cancelReq)).resolves.not.toThrow() @@ -169,7 +198,7 @@ describe('S3Locker', () => { renewalIntervalMs: 1000, maxRetries: 3, retryDelayMs: 200, - logger: { log: jest.fn(), warn: jest.fn(), error: jest.fn() }, + logger: { log: vi.fn(), warn: vi.fn(), error: vi.fn() }, }) const lock1 = locker.newLock('test-lock-1') @@ -177,7 +206,7 @@ describe('S3Locker', () => { const abortController1 = new AbortController() const abortController2 = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() // Both locks should succeed since they have different IDs await lock1.lock(abortController1.signal, cancelReq) @@ -196,7 +225,7 @@ describe('S3Locker', () => { const abortController1 = new AbortController() const abortController2 = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() // First lock await lock1.lock(abortController1.signal, cancelReq) @@ -218,7 +247,7 @@ describe('S3Locker', () => { const lock3 = locker.newLock('test-lock-3') const abortController = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() // All should succeed since they have different IDs await Promise.all([ @@ -239,7 +268,7 @@ describe('S3Locker', () => { // Create multiple locker instances to simulate different processes const lockers = Array.from( { length: numInstances }, - (_, index) => + () => new S3Locker({ s3Client, bucket: testBucket, @@ -249,13 +278,13 @@ describe('S3Locker', () => { renewalIntervalMs: 1000, maxRetries: 2, retryDelayMs: 150, - logger: { log: jest.fn(), warn: jest.fn(), error: jest.fn() }, + logger: { log: vi.fn(), warn: vi.fn(), error: vi.fn() }, }) ) // Each locker gets a unique lock ID const locks = lockers.map((locker, index) => locker.newLock(`unique-lock-${index}`)) - const cancelReq = jest.fn() + const cancelReq = vi.fn() // All lock attempts should succeed since they have unique IDs const lockPromises = locks.map(async (lock, index) => { @@ -284,7 +313,7 @@ describe('S3Locker', () => { test('should renew lock automatically', async () => { const lock = locker.newLock('renewable-lock') const abortController = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() await lock.lock(abortController.signal, cancelReq) @@ -305,16 +334,16 @@ describe('S3Locker', () => { renewalIntervalMs: 1800, // 1.8 seconds (less than TTL but long enough to prevent renewal) maxRetries: 5, retryDelayMs: 100, - logger: { log: jest.fn(), warn: jest.fn(), error: jest.fn() }, + logger: { log: vi.fn(), warn: vi.fn(), error: vi.fn() }, }) const lock1 = shortTtlLocker.newLock('expiring-lock') const abortController1 = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() // Acquire first lock await lock1.lock(abortController1.signal, cancelReq) - trackLock(lock1, shortTtlLocker) + trackLock(lock1) // Manually abort the first lock to stop its renewal timer abortController1.abort() @@ -366,9 +395,9 @@ describe('S3Locker', () => { Key: testLockKey, }) ) - fail('Lock should have been deleted') - } catch (error: any) { - expect(error.name).toBe('NoSuchKey') + throw new Error('Lock should have been deleted') + } catch (error) { + expect(getErrorDetails(error).name).toBe('NoSuchKey') } }) }) @@ -380,7 +409,7 @@ describe('S3Locker', () => { const abortController1 = new AbortController() const abortController2 = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() // First lock succeeds await lock1.lock(abortController1.signal, cancelReq) @@ -404,6 +433,100 @@ describe('S3Locker', () => { await lock1.unlock() }) + test('should not create zombie locks with rapid lock cycling and contention', async () => { + // This test reproduces the zombie lock race condition by using a spy to inject + // delays in the S3 client, simulating slow network between GET and PUT in renewLock: + // 1. Lock acquired, starts renewal timer (fires at T=1000) + // 2. Renewal GET completes, then we inject a delay before PUT + // 3. During the delay, unlock() DELETEs the lock + // 4. renewLock() PUT happens AFTER DELETE + // WITHOUT IfMatch: PUT succeeds, creating zombie + // WITH IfMatch: PUT fails (ETag mismatch), no zombie created + + const lockId = 'contention-race-test' + const cancelReq = vi.fn() + + // Spy on S3 client to inject delay after GET and before PUT in renewLock + const originalSend = s3Client.send.bind(s3Client) + const sendSpy = vi.spyOn(s3Client, 'send').mockImplementation(async (command) => { + const result = await originalSend(command) + + // Inject delay after GET in renewLock to simulate slow network + if (command instanceof GetObjectCommand && command.input.Key?.includes(lockId)) { + await new Promise((resolve) => setTimeout(resolve, 200)) + } + + return result + }) + + try { + for (let i = 0; i < 3; i++) { + const lock1 = locker.newLock(lockId) + const abortController1 = new AbortController() + + // Acquire first lock + const start = Date.now() + try { + await lock1.lock(abortController1.signal, cancelReq) + } catch (error) { + const errorDetails = getErrorDetails(error) + const lockDuration = Date.now() - start + throw new Error( + `Lock acquisition failed on iteration ${i} after ${lockDuration}ms with error: ${errorDetails.message}. This likely means a zombie lock exists from a previous iteration.` + ) + } + const lockDuration = Date.now() - start + + // Lock should be acquired quickly (< 1000ms) + // Longer times indicate retries due to zombie locks from previous iteration + if (lockDuration > 1000) { + await lock1.unlock() + throw new Error( + `Lock acquisition took ${lockDuration}ms on iteration ${i}, indicating a zombie lock exists` + ) + } + + // Wait for renewal timer to fire (1000ms) plus buffer + await new Promise((resolve) => setTimeout(resolve, 1100)) + + // Manually delete the lock to simulate unlock + await locker.releaseLock(lockId) + abortController1.abort() + + // Wait for the zombie-creating PUT to complete + // WITHOUT IfMatch fix: renewLock's in-flight PUT will succeed, creating a zombie + // WITH IfMatch fix: renewLock's PUT will fail (ETag mismatch), no zombie created + await new Promise((resolve) => setTimeout(resolve, 100)) + + // Check if a zombie lock exists + try { + const lockKey = `test-locks/${lockId}.lock` + await s3Client.send( + new GetObjectCommand({ + Bucket: testBucket, + Key: lockKey, + }) + ) + // If we got here, a lock exists - this is a zombie! + await locker.releaseLock(lockId) + throw new Error( + `Zombie lock detected on iteration ${i}! A lock exists after deletion, indicating the IfMatch fix is missing.` + ) + } catch (error) { + if (getErrorDetails(error).name === 'NoSuchKey') { + // Good - no zombie lock exists + continue + } + // Re-throw if it's our zombie detection error or other error + throw error + } + } + } finally { + // Restore original S3 client behavior + sendSpy.mockRestore() + } + }) + test('should handle unlock without lock', async () => { const lock = locker.newLock('test-lock-1') @@ -414,7 +537,7 @@ describe('S3Locker', () => { test('should handle double unlock', async () => { const lock = locker.newLock('test-lock-1') const abortController = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() await lock.lock(abortController.signal, cancelReq) await lock.unlock() @@ -434,12 +557,12 @@ describe('S3Locker', () => { renewalIntervalMs: 1000, maxRetries: 2, retryDelayMs: 100, - logger: { log: jest.fn(), warn: jest.fn(), error: jest.fn() }, + logger: { log: vi.fn(), warn: vi.fn(), error: vi.fn() }, }) const lock = invalidLocker.newLock('test-lock-1') const abortController = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() // Should fail with an error when trying to access non-existent bucket await expect(lock.lock(abortController.signal, cancelReq)).rejects.toThrow() @@ -480,7 +603,7 @@ describe('S3Locker', () => { const abortController1 = new AbortController() const abortController2 = new AbortController() const abortController3 = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() // First two locks should succeed (different IDs) await lock1.lock(abortController1.signal, cancelReq) @@ -501,12 +624,12 @@ describe('S3Locker', () => { await lock1.unlock() await lock2.unlock() - }, 10000) + }) test('should automatically renew locks', async () => { const lock = locker.newLock('renewal-test-lock') const abortController = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() await lock.lock(abortController.signal, cancelReq) @@ -523,7 +646,7 @@ describe('S3Locker', () => { const lock1 = locker.newLock(lockId) const abortController1 = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() // First acquisition await lock1.lock(abortController1.signal, cancelReq) @@ -541,7 +664,7 @@ describe('S3Locker', () => { test('should cleanup lock when stop signal is fired', async () => { const lock = locker.newLock('stop-signal-lock') const abortController = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() await lock.lock(abortController.signal, cancelReq) trackLock(lock) @@ -575,7 +698,7 @@ describe('S3Locker', () => { renewalIntervalMs: 1000, maxRetries: 3, retryDelayMs: 100, - logger: { log: jest.fn(), warn: jest.fn(), error: jest.fn() }, + logger: { log: vi.fn(), warn: vi.fn(), error: vi.fn() }, }) const lock1 = locker.newLock('isolation-lock') @@ -583,7 +706,7 @@ describe('S3Locker', () => { const abortController1 = new AbortController() const abortController2 = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() // First instance acquires lock await lock1.lock(abortController1.signal, cancelReq) @@ -594,7 +717,7 @@ describe('S3Locker', () => { await lock2.lock(abortController2.signal, cancelReq) // If it succeeds unexpectedly, unlock it await lock2.unlock() - } catch (error) { + } catch { // Expected behavior - lock contention secondLockFailed = true } @@ -615,7 +738,7 @@ describe('S3Locker', () => { const lock = locker.newLock('integration-test-lock') const abortController = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() // First, acquire the lock successfully await lock.lock(abortController.signal, cancelReq) @@ -640,7 +763,7 @@ describe('S3Locker', () => { test('should set up release notification listener when lock is acquired', async () => { const lock = locker.newLock('release-listener-test') const abortController = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() await lock.lock(abortController.signal, cancelReq) @@ -656,7 +779,7 @@ describe('S3Locker', () => { test('should unsubscribe from notifications when lock is released', async () => { const lock = locker.newLock('unsubscribe-test') const abortController = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() await lock.lock(abortController.signal, cancelReq) await lock.unlock() @@ -668,16 +791,19 @@ describe('S3Locker', () => { test('should call cancelReq when notifier triggers release', async () => { const lock = locker.newLock('cancel-req-test') const abortController = new AbortController() - const cancelReq = jest.fn() + const cancelReq = vi.fn() await lock.lock(abortController.signal, cancelReq) // Get the callback function that was registered with onRelease - const onReleaseCall = (mockNotifier.onRelease as jest.Mock).mock.calls.find( - ([id]) => id === 'cancel-req-test' + const onReleaseCall = (mockNotifier.onRelease as Mock).mock.calls.find( + (call) => call[0] === 'cancel-req-test' ) expect(onReleaseCall).toBeDefined() - const releaseCallback = onReleaseCall[1] + if (!onReleaseCall) { + throw new Error('Expected onRelease callback to be registered') + } + const releaseCallback = onReleaseCall[1] as () => void // Simulate the notifier triggering the release releaseCallback() diff --git a/src/test/s3-protocol.test.ts b/src/test/s3-protocol.test.ts index 57605ed34..2a8e67ee6 100644 --- a/src/test/s3-protocol.test.ts +++ b/src/test/s3-protocol.test.ts @@ -24,19 +24,26 @@ import { UploadPartCommand, UploadPartCopyCommand, } from '@aws-sdk/client-s3' -import { getConfig, mergeConfig } from '../config' -import app from '../app' -import { FastifyInstance } from 'fastify' import { Upload } from '@aws-sdk/lib-storage' -import { ReadableStreamBuffer } from 'stream-buffers' -import { randomUUID } from 'crypto' -import { getSignedUrl } from '@aws-sdk/s3-request-presigner' -import axios from 'axios' import { createPresignedPost } from '@aws-sdk/s3-presigned-post' +import { getSignedUrl } from '@aws-sdk/s3-request-presigner' import { wait } from '@internal/concurrency' +import { getPostgresConnection, getServiceKeyUser, TenantConnection } from '@internal/database' +import { DBMigration } from '@internal/database/migrations' +import { ERRORS } from '@internal/errors' +import { StorageKnexDB } from '@storage/database' +import { Uploader } from '@storage/uploader' +import axios from 'axios' +import { createHash, createHmac, randomUUID } from 'crypto' +import { FastifyInstance } from 'fastify' +import { ReadableStreamBuffer } from 'stream-buffers' +import app from '../app' +import { getConfig, mergeConfig } from '../config' +import { EMPTY_SHA256_HASH, SignatureV4, SignatureV4Service } from '../storage/protocols/s3' const { s3ProtocolAccessKeySecret, s3ProtocolAccessKeyId, storageS3Region } = getConfig() - +const STREAMING_PAYLOAD_ALGORITHM = 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD' +const STREAMING_TRAILER_PAYLOAD_ALGORITHM = 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER' async function createBucket(client: S3Client, name?: string, publicRead = true) { let bucketName: string if (!name) { @@ -60,10 +67,10 @@ async function uploadFile( bucketName: string, key: string, mb: number, - headers?: Record + headers?: Record ) { const uploader = new Upload({ - client: client, + client, params: { Bucket: bucketName, Key: key, @@ -76,18 +83,320 @@ async function uploadFile( return await uploader.done() } -jest.setTimeout(10 * 1000) +function formatAwsDate(date = new Date()) { + return date.toISOString().replace(/[:-]|\.\d{3}/g, '') +} + +function hmacSha256(key: string | Buffer, value: string) { + return createHmac('sha256', key).update(value).digest() +} + +function sha256Hex(value: Buffer) { + return createHash('sha256').update(value).digest('hex') +} + +function deriveSigningKey(secretKey: string, shortDate: string, region: string, service: string) { + const dateKey = hmacSha256(`AWS4${secretKey}`, shortDate) + const regionKey = hmacSha256(dateKey, region) + const serviceKey = hmacSha256(regionKey, service) + return hmacSha256(serviceKey, 'aws4_request') +} + +function createSignedChunk( + payload: Buffer, + previousSignature: string, + options: { + longDate: string + shortDate: string + region: string + service: string + secretKey: string + } +) { + const signingKey = deriveSigningKey( + options.secretKey, + options.shortDate, + options.region, + options.service + ) + const scope = `${options.shortDate}/${options.region}/${options.service}/aws4_request` + const chunkHash = sha256Hex(payload) + const stringToSign = [ + 'AWS4-HMAC-SHA256-PAYLOAD', + options.longDate, + scope, + previousSignature, + EMPTY_SHA256_HASH, + chunkHash, + ].join('\n') + const signature = createHmac('sha256', signingKey).update(stringToSign).digest('hex') + + return { + signature, + encoded: Buffer.concat([ + Buffer.from(`${payload.length.toString(16)};chunk-signature=${signature}\r\n`), + payload, + Buffer.from('\r\n'), + ]), + } +} + +async function sendAwsChunkedRequest(options: { + baseUrl: string + path: string + payload: Buffer + query?: Record +}) { + const signedRequest = await createSignedS3Request({ + baseUrl: options.baseUrl, + path: options.path, + method: 'PUT', + query: options.query, + contentSha: STREAMING_PAYLOAD_ALGORITHM, + headers: { + 'content-encoding': 'aws-chunked', + 'x-amz-decoded-content-length': options.payload.length.toString(), + }, + }) + const chunk = createSignedChunk(options.payload, signedRequest.signature, { + longDate: signedRequest.longDate, + shortDate: signedRequest.shortDate, + region: storageS3Region, + service: signedRequest.service, + secretKey: s3ProtocolAccessKeySecret!, + }) + const endChunk = createSignedChunk(Buffer.alloc(0), chunk.signature, { + longDate: signedRequest.longDate, + shortDate: signedRequest.shortDate, + region: storageS3Region, + service: signedRequest.service, + secretKey: s3ProtocolAccessKeySecret!, + }) + const encodedBody = Buffer.concat([chunk.encoded, endChunk.encoded]) + + const response = await fetch(signedRequest.requestUrl, { + method: 'PUT', + headers: { + ...signedRequest.headers, + 'content-length': encodedBody.length.toString(), + }, + body: encodedBody, + }) + + return { + status: response.status, + data: await response.text(), + } +} + +async function sendAwsChunkedTrailerModeWithoutTrailerRequest(options: { + baseUrl: string + path: string + payload: Buffer +}) { + const signedRequest = await createSignedS3Request({ + baseUrl: options.baseUrl, + path: options.path, + method: 'PUT', + contentSha: STREAMING_TRAILER_PAYLOAD_ALGORITHM, + headers: { + 'content-encoding': 'aws-chunked', + 'x-amz-decoded-content-length': options.payload.length.toString(), + 'x-amz-trailer': 'x-amz-checksum-crc32', + }, + }) + const chunk = createSignedChunk(options.payload, signedRequest.signature, { + longDate: signedRequest.longDate, + shortDate: signedRequest.shortDate, + region: storageS3Region, + service: signedRequest.service, + secretKey: s3ProtocolAccessKeySecret!, + }) + const endChunk = createSignedChunk(Buffer.alloc(0), chunk.signature, { + longDate: signedRequest.longDate, + shortDate: signedRequest.shortDate, + region: storageS3Region, + service: signedRequest.service, + secretKey: s3ProtocolAccessKeySecret!, + }) + const encodedBody = Buffer.concat([chunk.encoded, endChunk.encoded]) + + const response = await fetch(signedRequest.requestUrl, { + method: 'PUT', + headers: { + ...signedRequest.headers, + 'content-length': encodedBody.length.toString(), + }, + body: encodedBody, + }) + + return { + status: response.status, + data: await response.text(), + } +} + +async function createSignedS3Request(options: { + baseUrl: string + path: string + method: 'POST' | 'PUT' | 'GET' | 'DELETE' + body?: string | Buffer + query?: Record + headers?: Record + contentSha?: string + includeContentLength?: boolean +}) { + const longDate = formatAwsDate() + const shortDate = longDate.slice(0, 8) + const host = new URL(options.baseUrl).host + const service = SignatureV4Service.S3 + const payload = options.body ?? Buffer.alloc(0) + const payloadBuffer = typeof payload === 'string' ? Buffer.from(payload) : payload + const payloadHash = options.contentSha || sha256Hex(payloadBuffer) + const normalizedHeaders = Object.fromEntries( + Object.entries(options.headers || {}).map(([key, value]) => [key.toLowerCase(), value]) + ) + const signer = new SignatureV4({ + enforceRegion: false, + credentials: { + accessKey: s3ProtocolAccessKeyId!, + secretKey: s3ProtocolAccessKeySecret!, + region: storageS3Region, + service, + }, + }) + + const headers: Record = { + host, + 'x-amz-content-sha256': payloadHash, + 'x-amz-date': longDate, + ...(options.includeContentLength ? { 'content-length': payloadBuffer.length.toString() } : {}), + ...normalizedHeaders, + } + + const signedHeaders = Object.keys(headers) + .map((header) => header.toLowerCase()) + .sort() + + const clientSignature = { + credentials: { + accessKey: s3ProtocolAccessKeyId!, + shortDate, + region: storageS3Region, + service, + }, + signedHeaders, + signature: '', + longDate, + contentSha: payloadHash, + } + + const { signature } = await signer.sign(clientSignature, { + url: options.path, + method: options.method, + headers, + query: options.query, + body: options.body, + }) + + const requestUrl = new URL(`${options.baseUrl}${options.path}`) + + if (options.query) { + for (const [key, value] of Object.entries(options.query)) { + requestUrl.searchParams.set(key, value) + } + } + + return { + headers: { + ...headers, + authorization: + `AWS4-HMAC-SHA256 Credential=${s3ProtocolAccessKeyId}/${shortDate}/` + + `${storageS3Region}/${service}/aws4_request, SignedHeaders=${signedHeaders.join(';')}, ` + + `Signature=${signature}`, + }, + requestUrl, + service, + shortDate, + longDate, + signature, + } +} + +async function sendSignedS3Request(options: { + baseUrl: string + path: string + method: 'POST' | 'PUT' | 'GET' | 'DELETE' + body?: string + query?: Record + headers?: Record +}) { + const payload = options.body || '' + const signedRequest = await createSignedS3Request({ + ...options, + body: payload, + includeContentLength: true, + }) + + const response = await fetch(signedRequest.requestUrl, { + method: options.method, + headers: signedRequest.headers, + body: payload, + }) + + return { + status: response.status, + data: await response.text(), + } +} + +async function expectMultipartUploadToRemainPending( + client: S3Client, + options: { + bucket: string + key: string + uploadId: string + partETag: string + } +) { + try { + await client.send( + new HeadObjectCommand({ + Bucket: options.bucket, + Key: options.key, + }) + ) + throw new Error('Should not reach here') + } catch (e) { + expect((e as Error).message).not.toBe('Should not reach here') + expect((e as S3ServiceException).$metadata.httpStatusCode).toBe(404) + } + + const listPartsResp = await client.send( + new ListPartsCommand({ + Bucket: options.bucket, + Key: options.key, + UploadId: options.uploadId, + }) + ) + + expect(listPartsResp.Parts).toHaveLength(1) + expect(listPartsResp.Parts?.[0].PartNumber).toBe(1) + expect(listPartsResp.Parts?.[0].ETag).toBe(options.partETag) +} describe('S3 Protocol', () => { describe('Bucket', () => { let testApp: FastifyInstance let client: S3Client + let baseUrl: string beforeAll(async () => { testApp = app() const listener = await testApp.listen() + baseUrl = listener.replace('[::1]', 'localhost') client = new S3Client({ - endpoint: `${listener.replace('[::1]', 'localhost')}/s3`, + endpoint: `${baseUrl}/s3`, forcePathStyle: true, region: storageS3Region, credentials: { @@ -233,6 +542,36 @@ describe('S3 Protocol', () => { const resp = await client.send(listBuckets) expect(resp.Contents?.length).toBe(3) }) + + it('list all keys with pagination', async () => { + const bucket = await createBucket(client) + const listObjects = new ListObjectsCommand({ + Bucket: bucket, + Delimiter: '/', + MaxKeys: 1, + }) + + await Promise.all([ + uploadFile(client, bucket, 'test-1.jpg', 1), + uploadFile(client, bucket, 'prefix-1/test-1.jpg', 1), + uploadFile(client, bucket, 'prefix-3/test-1.jpg', 1), + uploadFile(client, bucket, 'prefix-4/test-1.jpg', 1), + ]) + + const resp = await client.send(listObjects) + expect(resp.CommonPrefixes?.length).toBe(1) + expect(resp.IsTruncated).toBe(true) + + const listObjects2 = new ListObjectsCommand({ + Bucket: bucket, + Delimiter: '/', + MaxKeys: 3, + Marker: resp.Marker, + }) + const resp2 = await client.send(listObjects2) + expect(resp2.CommonPrefixes?.length).toBe(2) + expect(resp2.Contents?.length).toBe(1) + }) }) describe('ListObjectsV2Command', () => { @@ -262,6 +601,47 @@ describe('S3 Protocol', () => { expect(resp.Contents?.length).toBe(3) }) + it('list objects with aws specific uri encoding', async () => { + const bucket = await createBucket(client) + const testObject1 = 'test (1).jpg' + const testObject2 = `prefix-1/test-1!'(123)*.jpg` + + await Promise.all([testObject1, testObject2].map((v) => uploadFile(client, bucket, v, 1))) + + const resp = await client.send( + new ListObjectsV2Command({ + Bucket: bucket, + }) + ) + expect(resp.Contents?.length).toBe(2) + + const resp2 = await client.send( + new ListObjectsV2Command({ + Bucket: bucket, + Prefix: 'test (', + }) + ) + expect(resp2.Contents?.length).toBe(1) + expect(resp2.Contents).toEqual([ + expect.objectContaining({ + Key: testObject1, + }), + ]) + + const resp3 = await client.send( + new ListObjectsV2Command({ + Bucket: bucket, + Prefix: `prefix-1/test-1!'(123)*`, + }) + ) + expect(resp3.Contents?.length).toBe(1) + expect(resp3.Contents).toEqual([ + expect.objectContaining({ + Key: testObject2, + }), + ]) + }) + it('list keys and common prefixes', async () => { const bucket = await createBucket(client) const listBuckets = new ListObjectsV2Command({ @@ -375,6 +755,56 @@ describe('S3 Protocol', () => { expect(objectsPage3.Contents?.[0].Key).toBe('test-1.jpg') expect(objectsPage3.IsTruncated).toBe(false) }) + + it('lists an entity-heavy first page without XML expansion failure', async () => { + const bucket = await createBucket(client) + const minEntityExpansions = 2000 + const pageSize = 10 + const entityRefsPerKey = Math.ceil(minEntityExpansions / pageSize) + const entityRun = '&'.repeat(entityRefsPerKey) + const names = Array.from({ length: pageSize + 1 }, (_, i) => { + return `key-${String(i).padStart(2, '0')}-${entityRun}.txt` + }) + + for (let i = 0; i < names.length; i += pageSize) { + await Promise.all( + names.slice(i, i + pageSize).map((name) => + client.send( + new PutObjectCommand({ + Bucket: bucket, + Key: name, + Body: Buffer.alloc(1), + ContentType: 'text/plain', + }) + ) + ) + ) + } + + const page1 = await client.send( + new ListObjectsV2Command({ + Bucket: bucket, + MaxKeys: pageSize, + }) + ) + + expect(page1.Contents).toHaveLength(pageSize) + expect(page1.IsTruncated).toBe(true) + expect(page1.NextContinuationToken).toBeTruthy() + expect(page1.Contents?.map((entry) => entry.Key)).toEqual(names.slice(0, pageSize)) + + const page2 = await client.send( + new ListObjectsV2Command({ + Bucket: bucket, + ContinuationToken: page1.NextContinuationToken, + MaxKeys: pageSize, + }) + ) + + expect(page2.Contents).toHaveLength(1) + expect(page2.IsTruncated).toBe(false) + expect(page2.Contents?.map((entry) => entry.Key)).toEqual(names.slice(pageSize)) + }, 60000) }) for (const urlEncode of [true, false]) { @@ -499,6 +929,19 @@ describe('S3 Protocol', () => { expect(resp.UploadId).toBeTruthy() }) + it('creates a multi part upload for a json object', async () => { + const bucketName = await createBucket(client) + const createMultiPartUpload = new CreateMultipartUploadCommand({ + Bucket: bucketName, + Key: 'test-1.json', + ContentType: 'application/json', + CacheControl: 'max-age=2000', + }) + + const resp = await client.send(createMultiPartUpload) + expect(resp.UploadId).toBeTruthy() + }) + it('upload a part', async () => { const bucketName = await createBucket(client) const createMultiPartUpload = new CreateMultipartUploadCommand({ @@ -567,6 +1010,249 @@ describe('S3 Protocol', () => { expect(completeResp.Key).toEqual('test-1.jpg') }) + it('does not complete multipart upload on malformed xml body', async () => { + const bucketName = await createBucket(client) + const key = 'test-explicit-parts.xml' + const createMultiPartUpload = new CreateMultipartUploadCommand({ + Bucket: bucketName, + Key: key, + ContentType: 'application/xml', + CacheControl: 'max-age=2000', + }) + const resp = await client.send(createMultiPartUpload) + expect(resp.UploadId).toBeTruthy() + + const part1Body = Buffer.alloc(1024 * 5, 'a') + + const part1 = await client.send( + new UploadPartCommand({ + Bucket: bucketName, + Key: key, + ContentLength: part1Body.length, + UploadId: resp.UploadId, + Body: part1Body, + PartNumber: 1, + }) + ) + + const malformedCompleteResp = await sendSignedS3Request({ + baseUrl, + method: 'POST', + path: `/s3/${bucketName}/${key}`, + query: { + uploadId: resp.UploadId!, + }, + headers: { + 'Content-Type': 'application/xml', + }, + body: '', + }) + + expect(malformedCompleteResp.status).toBe(400) + expect(malformedCompleteResp.data).toContain('') + expect(malformedCompleteResp.data).toContain('InvalidRequest') + expect(malformedCompleteResp.data).toContain('Invalid XML payload:') + + await expectMultipartUploadToRemainPending(client, { + bucket: bucketName, + key, + uploadId: resp.UploadId!, + partETag: part1.ETag!, + }) + + const completeResp = await client.send( + new CompleteMultipartUploadCommand({ + Bucket: bucketName, + Key: key, + UploadId: resp.UploadId, + MultipartUpload: { + Parts: [ + { + PartNumber: 1, + ETag: part1.ETag, + }, + ], + }, + }) + ) + + expect(completeResp.$metadata.httpStatusCode).toBe(200) + + const getResp = await client.send( + new GetObjectCommand({ + Bucket: bucketName, + Key: key, + }) + ) + + const data = await getResp.Body?.transformToByteArray() + expect(Buffer.from(data || [])).toEqual(part1Body) + expect(part1.ETag).toBeTruthy() + }) + + it('does not complete multipart upload on malformed json body', async () => { + const bucketName = await createBucket(client) + const key = 'test-invalid-json-complete.bin' + const createMultiPartUpload = new CreateMultipartUploadCommand({ + Bucket: bucketName, + Key: key, + ContentType: 'application/octet-stream', + CacheControl: 'max-age=2000', + }) + const resp = await client.send(createMultiPartUpload) + expect(resp.UploadId).toBeTruthy() + + const part1Body = Buffer.alloc(1024 * 5, 'b') + + const part1 = await client.send( + new UploadPartCommand({ + Bucket: bucketName, + Key: key, + ContentLength: part1Body.length, + UploadId: resp.UploadId, + Body: part1Body, + PartNumber: 1, + }) + ) + + const malformedCompleteResp = await sendSignedS3Request({ + baseUrl, + method: 'POST', + path: `/s3/${bucketName}/${key}`, + query: { + uploadId: resp.UploadId!, + }, + headers: { + 'content-type': 'application/json', + }, + body: '{"Parts":', + }) + + expect(malformedCompleteResp.status).toBe(400) + expect(malformedCompleteResp.data).toContain('') + expect(malformedCompleteResp.data).toContain('InvalidRequest') + expect(malformedCompleteResp.data).toContain( + "Body is not valid JSON but content-type is set to 'application/json'" + ) + + await expectMultipartUploadToRemainPending(client, { + bucket: bucketName, + key, + uploadId: resp.UploadId!, + partETag: part1.ETag!, + }) + + const completeResp = await client.send( + new CompleteMultipartUploadCommand({ + Bucket: bucketName, + Key: key, + UploadId: resp.UploadId, + MultipartUpload: { + Parts: [ + { + PartNumber: 1, + ETag: part1.ETag, + }, + ], + }, + }) + ) + + expect(completeResp.$metadata.httpStatusCode).toBe(200) + + const getResp = await client.send( + new GetObjectCommand({ + Bucket: bucketName, + Key: key, + }) + ) + + const data = await getResp.Body?.transformToByteArray() + expect(Buffer.from(data || [])).toEqual(part1Body) + expect(part1.ETag).toBeTruthy() + }) + + it('does not complete multipart upload on empty json body', async () => { + const bucketName = await createBucket(client) + const key = 'test-empty-json-complete.bin' + const createMultiPartUpload = new CreateMultipartUploadCommand({ + Bucket: bucketName, + Key: key, + ContentType: 'application/octet-stream', + CacheControl: 'max-age=2000', + }) + const resp = await client.send(createMultiPartUpload) + expect(resp.UploadId).toBeTruthy() + + const part1Body = Buffer.alloc(1024 * 5, 'c') + + const part1 = await client.send( + new UploadPartCommand({ + Bucket: bucketName, + Key: key, + ContentLength: part1Body.length, + UploadId: resp.UploadId, + Body: part1Body, + PartNumber: 1, + }) + ) + + const emptyJsonCompleteResp = await sendSignedS3Request({ + baseUrl, + method: 'POST', + path: `/s3/${bucketName}/${key}`, + query: { + uploadId: resp.UploadId!, + }, + headers: { + 'content-type': 'application/json', + }, + }) + + expect(emptyJsonCompleteResp.status).toBe(400) + expect(emptyJsonCompleteResp.data).toContain('') + expect(emptyJsonCompleteResp.data).toContain('InvalidRequest') + expect(emptyJsonCompleteResp.data).toContain( + "Body cannot be empty when content-type is set to 'application/json'" + ) + + await expectMultipartUploadToRemainPending(client, { + bucket: bucketName, + key, + uploadId: resp.UploadId!, + partETag: part1.ETag!, + }) + + const completeResp = await client.send( + new CompleteMultipartUploadCommand({ + Bucket: bucketName, + Key: key, + UploadId: resp.UploadId, + MultipartUpload: { + Parts: [ + { + PartNumber: 1, + ETag: part1.ETag, + }, + ], + }, + }) + ) + + expect(completeResp.$metadata.httpStatusCode).toBe(200) + + const getResp = await client.send( + new GetObjectCommand({ + Bucket: bucketName, + Key: key, + }) + ) + + const data = await getResp.Body?.transformToByteArray() + expect(Buffer.from(data || [])).toEqual(part1Body) + expect(part1.ETag).toBeTruthy() + }) + it('aborts a multipart upload', async () => { const bucketName = await createBucket(client) const createMultiPartUpload = new CreateMultipartUploadCommand({ @@ -679,6 +1365,64 @@ describe('S3 Protocol', () => { } }) + it('accepts aws-chunked putObject bodies when decoded size is within the limit', async () => { + const bucketName = await createBucket(client) + const key = 'test-aws-chunked-put-object.jpg' + const payload = Buffer.alloc(123, 1) + + mergeConfig({ + uploadFileSizeLimit: 150, + }) + + const response = await sendAwsChunkedRequest({ + baseUrl, + path: `/s3/${bucketName}/${key}`, + payload, + }) + + expect(response.status).toBe(200) + + const headObject = await client.send( + new HeadObjectCommand({ + Bucket: bucketName, + Key: key, + }) + ) + + expect(headObject.ContentLength).toBe(payload.length) + }) + + it('rejects trailer-mode aws-chunked putObject bodies without a trailer block', async () => { + const bucketName = await createBucket(client) + const key = 'test-aws-chunked-put-object-empty-trailer.jpg' + const payload = Buffer.alloc(123, 1) + + mergeConfig({ + uploadFileSizeLimit: 150, + }) + + const response = await sendAwsChunkedTrailerModeWithoutTrailerRequest({ + baseUrl, + path: `/s3/${bucketName}/${key}`, + payload, + }) + + expect(response.status).toBeGreaterThanOrEqual(400) + + try { + await client.send( + new HeadObjectCommand({ + Bucket: bucketName, + Key: key, + }) + ) + throw new Error('Should not reach here') + } catch (e) { + expect((e as Error).message).not.toEqual('Should not reach here') + expect((e as S3ServiceException).$metadata.httpStatusCode).toEqual(404) + } + }) + it('will not allow uploading a file that exceeded the maxFileSize', async () => { const bucketName = await createBucket(client) @@ -687,7 +1431,7 @@ describe('S3 Protocol', () => { }) const uploader = new Upload({ - client: client, + client, leavePartsOnError: true, params: { @@ -744,24 +1488,64 @@ describe('S3 Protocol', () => { ContentLength: 1024 * 12, }) - try { - await client.send(uploadPart) - throw new Error('Should not reach here') - } catch (e) { - expect((e as Error).message).not.toEqual('Should not reach here') - expect((e as S3ServiceException).$metadata.httpStatusCode).toEqual(413) - expect((e as S3ServiceException).message).toEqual( - 'The object exceeded the maximum allowed size' - ) - expect((e as S3ServiceException).name).toEqual('EntityTooLarge') - } + try { + await client.send(uploadPart) + throw new Error('Should not reach here') + } catch (e) { + expect((e as Error).message).not.toEqual('Should not reach here') + expect((e as S3ServiceException).$metadata.httpStatusCode).toEqual(413) + expect((e as S3ServiceException).message).toEqual( + 'The object exceeded the maximum allowed size' + ) + expect((e as S3ServiceException).name).toEqual('EntityTooLarge') + } + }) + + it('accepts aws-chunked uploadPart bodies when decoded size is within the limit', async () => { + const bucketName = await createBucket(client, 'chunked-part') + const payload = Buffer.alloc(123, 2) + + mergeConfig({ + uploadFileSizeLimit: 150, + }) + + const multipart = await client.send( + new CreateMultipartUploadCommand({ + Bucket: bucketName, + Key: 'test-aws-chunked-upload-part.jpg', + ContentType: 'image/jpg', + CacheControl: 'max-age=2000', + }) + ) + + const response = await sendAwsChunkedRequest({ + baseUrl, + path: `/s3/${bucketName}/test-aws-chunked-upload-part.jpg`, + payload, + query: { + uploadId: multipart.UploadId!, + partNumber: '1', + }, + }) + + expect(response.status).toBe(200) + + const listedParts = await client.send( + new ListPartsCommand({ + Bucket: bucketName, + Key: 'test-aws-chunked-upload-part.jpg', + UploadId: multipart.UploadId, + }) + ) + + expect(listedParts.Parts?.map((part) => part.PartNumber)).toEqual([1]) }) it('upload a file using multipart upload', async () => { const bucketName = await createBucket(client) const uploader = new Upload({ - client: client, + client, params: { Bucket: bucketName, Key: 'test-1.jpg', @@ -774,6 +1558,78 @@ describe('S3 Protocol', () => { expect(resp.$metadata).toBeTruthy() }) + + it('does not mutate in_progress_size when canUpload (RLS) fails', async () => { + /* + Calling shouldAllowPartUpload mutates the in_progress_size so we have to ensure + canUpload is called beforehand or else it can cause issues for valid uploads. + + This test sets the fileSizeLimit to 10kb and each part at 5kb. It simulates + first request successful, second failed, third passes. If the in_progress_size + was mutated on the second request it would be at 10kb causing the third to fail. + */ + const bucketName = await createBucket(client) + const key = 'rls-ordering-test.jpg' + const partSize = 1024 * 5 + + mergeConfig({ uploadFileSizeLimit: partSize * 2 }) + + const createResp = await client.send( + new CreateMultipartUploadCommand({ + Bucket: bucketName, + Key: key, + ContentType: 'image/jpg', + }) + ) + expect(createResp.UploadId).toBeTruthy() + const uploadId = createResp.UploadId + + const part1Resp = await client.send( + new UploadPartCommand({ + Bucket: bucketName, + Key: key, + UploadId: uploadId, + PartNumber: 1, + Body: Buffer.alloc(partSize), + ContentLength: partSize, + }) + ) + expect(part1Resp.ETag).toBeTruthy() + + const canUploadSpy = vi + .spyOn(Uploader.prototype, 'canUpload') + .mockRejectedValueOnce(ERRORS.AccessDenied('upload')) + + try { + await client.send( + new UploadPartCommand({ + Bucket: bucketName, + Key: key, + UploadId: uploadId, + PartNumber: 2, + Body: Buffer.alloc(partSize), + ContentLength: partSize, + }) + ) + throw new Error('Should not reach here') + } catch (e) { + expect((e as Error).message).not.toEqual('Should not reach here') + } finally { + canUploadSpy.mockRestore() + } + + const part2Resp = await client.send( + new UploadPartCommand({ + Bucket: bucketName, + Key: key, + UploadId: uploadId, + PartNumber: 2, + Body: Buffer.alloc(partSize), + ContentLength: partSize, + }) + ) + expect(part2Resp.ETag).toBeTruthy() + }) }) describe('GetObject', () => { @@ -935,7 +1791,7 @@ describe('S3 Protocol', () => { expect(resp.Contents).toBe(undefined) }) - it('try to delete multiple objects that dont exists', async () => { + it('try to delete multiple objects that dont exist', async () => { const bucketName = await createBucket(client) await uploadFile(client, bucketName, 'test-1.jpg', 1) @@ -967,14 +1823,12 @@ describe('S3 Protocol', () => { { Key: 'test-2.jpg', Code: 'AccessDenied', - Message: - "You do not have permission to delete this object or the object doesn't exists", + Message: "You do not have permission to delete this object or the object doesn't exist", }, { Key: 'test-3.jpg', Code: 'AccessDenied', - Message: - "You do not have permission to delete this object or the object doesn't exists", + Message: "You do not have permission to delete this object or the object doesn't exist", }, ]) @@ -1071,14 +1925,14 @@ describe('S3 Protocol', () => { expect(headObj.CacheControl).toBe('max-age=2009') }) - it('will not be able to copy an object that doesnt exists', async () => { + it('will not be able to copy an object that doesnt exist', async () => { const bucketName1 = await createBucket(client) await uploadFile(client, bucketName1, 'test-copy-1.jpg', 1) const copyObjectCommand = new CopyObjectCommand({ Bucket: bucketName1, Key: 'test-copied-2.jpg', - CopySource: `${bucketName1}/test-dont-exists.jpg`, + CopySource: `${bucketName1}/test-doesnt-exist.jpg`, }) try { @@ -1152,6 +2006,62 @@ describe('S3 Protocol', () => { expect(resp.Uploads?.[2].Key).toBe('test-3.jpg') expect(resp.CommonPrefixes?.[0].Prefix).toBe('nested/') }) + + it('treats % as a literal character in multipart prefix filtering with delimiter', async () => { + const bucketName = await createBucket(client) + const createMultiPartUpload = (key: string) => + new CreateMultipartUploadCommand({ + Bucket: bucketName, + Key: key, + ContentType: 'image/jpg', + CacheControl: 'max-age=2000', + }) + + await Promise.all([ + client.send(createMultiPartUpload(`percent-${randomUUID()}.jpg`)), + client.send(createMultiPartUpload(`percent-${randomUUID()}.jpg`)), + ]) + + const listMultipartUploads = new ListMultipartUploadsCommand({ + Bucket: bucketName, + Delimiter: '/', + Prefix: '%', + }) + + const resp = await client.send(listMultipartUploads) + expect(resp.Uploads).toBeUndefined() + expect(resp.CommonPrefixes).toBeUndefined() + }) + + it('treats _ as a literal character in multipart prefix filtering with delimiter', async () => { + const bucketName = await createBucket(client) + const runId = randomUUID() + const literalMatchKey = `wild_${runId}/hit.jpg` + const wildcardOnlyMatchKey = `wildX${runId}/miss.jpg` + const createMultiPartUpload = (key: string) => + new CreateMultipartUploadCommand({ + Bucket: bucketName, + Key: key, + ContentType: 'image/jpg', + CacheControl: 'max-age=2000', + }) + + await Promise.all([ + client.send(createMultiPartUpload(literalMatchKey)), + client.send(createMultiPartUpload(wildcardOnlyMatchKey)), + ]) + + const listMultipartUploads = new ListMultipartUploadsCommand({ + Bucket: bucketName, + Delimiter: '/', + Prefix: `wild_${runId}/`, + }) + + const resp = await client.send(listMultipartUploads) + expect(resp.CommonPrefixes).toBeUndefined() + expect(resp.Uploads?.length).toBe(1) + expect(resp.Uploads?.[0].Key).toBe(literalMatchKey) + }) }) it('will list multipart uploads with delimiter and pagination', async () => { @@ -1442,7 +2352,7 @@ describe('S3 Protocol', () => { const resp = await fetch(uploadUrl, { method: 'PUT', - body: body, + body, headers: { 'Content-Length': body.length.toString(), }, @@ -1470,6 +2380,335 @@ describe('S3 Protocol', () => { expect(resp.ok).toBeTruthy() }) + + it('supports response-content-disposition override', async () => { + const bucket = await createBucket(client) + const key = 'test-disposition.jpg' + + await uploadFile(client, bucket, key, 2) + + const response = await client.send( + new GetObjectCommand({ + Bucket: bucket, + Key: key, + ResponseContentDisposition: 'attachment; filename="custom-name.txt"', + }) + ) + + expect(response.ContentDisposition).toBe('attachment; filename="custom-name.txt"') + }) + + it('supports response-content-disposition override via presigned URL', async () => { + const bucket = await createBucket(client) + const key = 'test-presigned-disposition.jpg' + + await uploadFile(client, bucket, key, 2) + + const getUrl = await getSignedUrl( + client, + new GetObjectCommand({ + Bucket: bucket, + Key: key, + ResponseContentDisposition: 'attachment; filename="presigned.pdf"', + }), + { expiresIn: 100 } + ) + + const resp = await fetch(getUrl) + + expect(resp.ok).toBeTruthy() + expect(resp.headers.get('content-disposition')).toBe('attachment; filename="presigned.pdf"') + }) + + it('supports response-content-type override', async () => { + const bucket = await createBucket(client) + const key = 'test-content-type.jpg' + + await uploadFile(client, bucket, key, 2) + + const response = await client.send( + new GetObjectCommand({ + Bucket: bucket, + Key: key, + ResponseContentType: 'text/plain', + }) + ) + + expect(response.ContentType).toBe('text/plain') + }) + + it('supports response-cache-control override', async () => { + const bucket = await createBucket(client) + const key = 'test-cache-control.jpg' + + await uploadFile(client, bucket, key, 2) + + const response = await client.send( + new GetObjectCommand({ + Bucket: bucket, + Key: key, + ResponseCacheControl: 'no-cache, no-store', + }) + ) + + expect(response.CacheControl).toBe('no-cache, no-store') + }) + + it('supports multiple response overrides simultaneously', async () => { + const bucket = await createBucket(client) + const key = 'test-multiple-overrides.jpg' + + await uploadFile(client, bucket, key, 2) + + const response = await client.send( + new GetObjectCommand({ + Bucket: bucket, + Key: key, + ResponseContentDisposition: 'inline; filename="test.txt"', + ResponseContentType: 'application/octet-stream', + ResponseCacheControl: 'max-age=0', + ResponseContentLanguage: 'en-US', + ResponseContentEncoding: 'gzip', + }) + ) + + expect(response.ContentDisposition).toBe('inline; filename="test.txt"') + expect(response.ContentType).toBe('application/octet-stream') + expect(response.CacheControl).toBe('max-age=0') + expect(response.ContentLanguage).toBe('en-US') + expect(response.ContentEncoding).toBe('gzip') + }) + + it('supports response-expires override', async () => { + const bucket = await createBucket(client) + const key = 'test-expires.jpg' + + await uploadFile(client, bucket, key, 2) + + const expiresDate = new Date('2030-01-01T00:00:00Z') + + const response = await client.send( + new GetObjectCommand({ + Bucket: bucket, + Key: key, + ResponseExpires: expiresDate, + }) + ) + + expect(response.ExpiresString).toEqual(expiresDate.toUTCString()) + }) + + it('rejects response-content-disposition with invalid characters', async () => { + const bucket = await createBucket(client) + const key = 'test-disposition-reject.jpg' + + await uploadFile(client, bucket, key, 2) + + const response = await client.send( + new GetObjectCommand({ + Bucket: bucket, + Key: key, + ResponseContentDisposition: 'attachment; filename="test\n\r\0.txt"', + }) + ) + // invalid content-disposition header removed + expect(response.ContentDisposition).toBeUndefined() + }) + + it('rejects response-content-type with invalid characters', async () => { + const bucket = await createBucket(client) + const key = 'test-content-type-reject.jpg' + + await uploadFile(client, bucket, key, 2) + + const response = await client.send( + new GetObjectCommand({ + Bucket: bucket, + Key: key, + ResponseContentType: 'text/html\nX-Evil: injection\r\0', + }) + ) + // invalid content-type header rejected, default used + expect(response.ContentType).toBe('image/jpg') + }) + + it('rejects response-cache-control with invalid characters', async () => { + const bucket = await createBucket(client) + const key = 'test-cache-control-reject.jpg' + + await uploadFile(client, bucket, key, 2) + + const response = await client.send( + new GetObjectCommand({ + Bucket: bucket, + Key: key, + ResponseCacheControl: 'no-cache\nX-Evil: header\r\0', + }) + ) + // invalid cache-control header rejected, default used + expect(response.CacheControl).toBe('no-cache') + }) + + it('rejects response-content-encoding with invalid characters', async () => { + const bucket = await createBucket(client) + const key = 'test-content-encoding-reject.jpg' + + await uploadFile(client, bucket, key, 2) + + const response = await client.send( + new GetObjectCommand({ + Bucket: bucket, + Key: key, + ResponseContentEncoding: 'gzip\nX-Evil: header\r\0', + }) + ) + // invalid content-encoding header removed + expect(response.ContentEncoding).toBeUndefined() + }) + + it('rejects response-content-language with invalid characters', async () => { + const bucket = await createBucket(client) + const key = 'test-content-language-reject.jpg' + + await uploadFile(client, bucket, key, 2) + + const response = await client.send( + new GetObjectCommand({ + Bucket: bucket, + Key: key, + ResponseContentLanguage: 'en-US\nX-Evil: header\r\0', + }) + ) + // invalid content-language header removed + expect(response.ContentLanguage).toBeUndefined() + }) + }) + }) +}) + +describe('Migration compatibility', () => { + describe('integration', () => { + const { tenantId } = getConfig() + let connection: TenantConnection + let bucketId: string + + beforeAll(async () => { + const adminUser = await getServiceKeyUser(tenantId) + connection = await getPostgresConnection({ + tenantId, + user: adminUser, + superUser: adminUser, + host: 'localhost', + }) + + bucketId = randomUUID() + const db = new StorageKnexDB(connection, { tenantId, host: 'localhost' }) + await db.createBucket({ id: bucketId, name: `migration-test-${bucketId}`, public: false }) + }) + + afterAll(async () => { + const db = new StorageKnexDB(connection, { tenantId, host: 'localhost' }) + await db.deleteBucket(bucketId) + await connection.dispose() + }) + + const makeDB = (latestMigration?: keyof typeof DBMigration) => + new StorageKnexDB(connection, { tenantId, host: 'localhost', latestMigration }) + + describe('createMultipartUpload', () => { + it('does not store metadata when latestMigration is before s3-multipart-uploads-metadata', async () => { + const db = makeDB('fix-optimized-search-function') // migration 56 + const uploadId = randomUUID() + try { + const result = await db.createMultipartUpload( + uploadId, + bucketId, + 'test-pre-migration.txt', + randomUUID(), + 'sig', + undefined, + undefined, + { + cacheControl: 'no-cache', + contentLength: 0, + size: 0, + mimetype: 'text/plain', + eTag: 'abc', + } + ) + expect(result.metadata).toBeNull() + } finally { + await makeDB().deleteMultipartUpload(uploadId) + } + }) + + it('stores metadata when latestMigration is s3-multipart-uploads-metadata', async () => { + const db = makeDB('s3-multipart-uploads-metadata') // migration 57 + const uploadId = randomUUID() + const metadata = { + cacheControl: 'no-cache', + contentLength: 0, + size: 0, + mimetype: 'text/plain', + eTag: 'abc', + } + try { + const result = await db.createMultipartUpload( + uploadId, + bucketId, + 'test-post-migration.txt', + randomUUID(), + 'sig', + undefined, + undefined, + metadata + ) + expect(result.metadata).toEqual(metadata) + } finally { + await makeDB().deleteMultipartUpload(uploadId) + } + }) + }) + + describe('findMultipartUpload', () => { + let uploadId: string + + beforeAll(async () => { + uploadId = randomUUID() + const db = makeDB('s3-multipart-uploads-metadata') + await db.createMultipartUpload( + uploadId, + bucketId, + 'test-find.txt', + randomUUID(), + 'sig', + undefined, + undefined, + { + cacheControl: 'no-cache', + contentLength: 0, + size: 0, + mimetype: 'text/plain', + eTag: 'abc', + } + ) + }) + + afterAll(async () => { + await makeDB().deleteMultipartUpload(uploadId) + }) + + it('excludes metadata from result when latestMigration is before s3-multipart-uploads-metadata', async () => { + const db = makeDB('fix-optimized-search-function') // migration 56 + const result = await db.findMultipartUpload(uploadId, 'id,version,metadata') + expect(result).not.toHaveProperty('metadata') + }) + + it('includes metadata in result when latestMigration is s3-multipart-uploads-metadata', async () => { + const db = makeDB('s3-multipart-uploads-metadata') // migration 57 + const result = await db.findMultipartUpload(uploadId, 'id,version,metadata') + expect(result).toHaveProperty('metadata') + }) }) }) }) diff --git a/src/test/scanner.test.ts b/src/test/scanner.test.ts index ab4cbbe3c..065f6c04a 100644 --- a/src/test/scanner.test.ts +++ b/src/test/scanner.test.ts @@ -1,8 +1,9 @@ -import { useStorage } from './utils/storage' -import { Readable } from 'stream' import { eachParallel } from '@internal/testing/generators/array' -import { getConfig } from '../config' +import { withOptionalVersion } from '@storage/backend' import { randomUUID } from 'crypto' +import { Readable } from 'stream' +import { getConfig } from '../config' +import { useStorage } from './utils/storage' const { storageS3Bucket, tenantId } = getConfig() @@ -28,7 +29,6 @@ describe('ObjectScanner', () => { body: Readable.from(Buffer.from('test')), mimeType: 'text/plain', cacheControl: 'no-cache', - userMetadata: {}, isTruncated: () => false, }, }) @@ -95,12 +95,11 @@ describe('ObjectScanner', () => { body: Readable.from(Buffer.from('test')), mimeType: 'text/plain', cacheControl: 'no-cache', - userMetadata: {}, isTruncated: () => false, }, }) - return { name: upload.obj.name } + return { name: upload.obj.name, version: upload.obj.version } }) const numToDelete = 10 @@ -133,22 +132,21 @@ describe('ObjectScanner', () => { const s3ObjectAll = [] let nextToken = '' - while (true) { + do { const s3Objects = await storage.adapter.list(storageS3Bucket, { prefix: `${tenantId}/${bucket.id}`, - nextToken: nextToken, + nextToken, }) s3ObjectAll.push(...s3Objects.keys) - if (!s3Objects.nextToken) { - break - } - nextToken = s3Objects.nextToken - } + nextToken = s3Objects.nextToken ?? '' + } while (nextToken) // Check s3 files are deleted expect(s3ObjectAll).toHaveLength(maxUploads - numToDelete) // Compare the keys names - expect(s3ObjectAll.length).not.toContain(objectsToDelete.map((o) => `${bucket.id}/${o.name}`)) + expect(s3ObjectAll.map((o) => o.name)).not.toEqual( + expect.arrayContaining(objectsToDelete.map((o) => withOptionalVersion(o.name, o.version))) + ) // Check files are backed-up const backupFiles = await storage.adapter.list(storageS3Bucket, { diff --git a/src/test/search-filters.test.ts b/src/test/search-filters.test.ts new file mode 100644 index 000000000..4c4b38cea --- /dev/null +++ b/src/test/search-filters.test.ts @@ -0,0 +1,141 @@ +import { randomUUID } from 'crypto' +import { FastifyInstance } from 'fastify' +import app from '../app' +import { getConfig } from '../config' + +const { serviceKeyAsync } = getConfig() + +describe('search filter wildcard escaping', () => { + let appInstance: FastifyInstance + let serviceKey: string + + beforeAll(async () => { + serviceKey = await serviceKeyAsync + appInstance = app() + }) + + afterAll(async () => { + await appInstance.close() + }) + + test('bucket search should treat % as a literal character', async () => { + const response = await appInstance.inject({ + method: 'GET', + url: '/bucket?search=%25', + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + expect(response.statusCode).toBe(200) + const buckets = response.json<{ name: string }[]>() + expect(buckets).toHaveLength(0) + }) + + test('bucket search should treat _ as a literal character', async () => { + const runId = randomUUID().slice(0, 8) + const literalMatch = `escwild_${runId}` + const wildcardOnlyMatch = `escwildX${runId}` + + const createBucket = async (name: string) => + appInstance.inject({ + method: 'POST', + url: '/bucket', + headers: { + authorization: `Bearer ${serviceKey}`, + }, + payload: { name }, + }) + + const deleteBucket = async (name: string) => + appInstance.inject({ + method: 'DELETE', + url: `/bucket/${name}`, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + await createBucket(literalMatch) + await createBucket(wildcardOnlyMatch) + + try { + const response = await appInstance.inject({ + method: 'GET', + url: `/bucket?search=${encodeURIComponent(`escwild_${runId}`)}`, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + expect(response.statusCode).toBe(200) + const names = response.json<{ name: string }[]>().map((bucket) => bucket.name) + expect(names).toContain(literalMatch) + expect(names).not.toContain(wildcardOnlyMatch) + } finally { + await deleteBucket(literalMatch) + await deleteBucket(wildcardOnlyMatch) + } + }) + + test('analytics bucket search should treat % as a literal character', async () => { + const response = await appInstance.inject({ + method: 'GET', + url: '/iceberg/bucket?search=%25', + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + expect(response.statusCode).toBe(200) + const buckets = response.json<{ name: string }[]>() + expect(buckets).toHaveLength(0) + }) + + test('analytics bucket search should treat _ as a literal character', async () => { + const runId = randomUUID().slice(0, 8) + const literalMatch = `icewild_${runId}` + const wildcardOnlyMatch = `icewildX${runId}` + + const createBucket = async (name: string) => + appInstance.inject({ + method: 'POST', + url: '/iceberg/bucket', + headers: { + authorization: `Bearer ${serviceKey}`, + 'Content-Type': 'application/json', + }, + payload: { name }, + }) + + const deleteBucket = async (name: string) => + appInstance.inject({ + method: 'DELETE', + url: `/iceberg/bucket/${name}`, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + await createBucket(literalMatch) + await createBucket(wildcardOnlyMatch) + + try { + const response = await appInstance.inject({ + method: 'GET', + url: `/iceberg/bucket?search=${encodeURIComponent(`icewild_${runId}`)}`, + headers: { + authorization: `Bearer ${serviceKey}`, + }, + }) + + expect(response.statusCode).toBe(200) + const names = response.json<{ name: string }[]>().map((bucket) => bucket.name) + expect(names).toContain(literalMatch) + expect(names).not.toContain(wildcardOnlyMatch) + } finally { + await deleteBucket(literalMatch) + await deleteBucket(wildcardOnlyMatch) + } + }) +}) diff --git a/src/test/sharding.test.ts b/src/test/sharding.test.ts new file mode 100644 index 000000000..f8e89e348 --- /dev/null +++ b/src/test/sharding.test.ts @@ -0,0 +1,817 @@ +import { multitenantKnex } from '@internal/database' +import { runMultitenantMigrations } from '@internal/database/migrations' +import { KnexShardStoreFactory, ShardCatalog, SingleShard } from '@internal/sharding' +import { + ExpiredReservationError, + NoActiveShardError, + ReservationNotFoundError, +} from '@internal/sharding/errors' +import { randomUUID } from 'crypto' +import { Knex } from 'knex' +import { useStorage } from './utils/storage' + +describe('Sharding System', () => { + const storageTest = useStorage() + let db: Knex + let storeFactory: KnexShardStoreFactory + let catalog: ShardCatalog + + beforeAll(async () => { + db = multitenantKnex + storeFactory = new KnexShardStoreFactory(db) + catalog = new ShardCatalog(storeFactory) + + await runMultitenantMigrations() + }) + + afterAll(async () => { + await storageTest.database.connection.dispose() + }) + + beforeEach(async () => { + // Clean up sharding tables before each test + await db('shard_reservation').delete() + await db('shard_slots').delete() + await db('shard').delete() + }) + + describe('Shard Management', () => { + it('should create a shard successfully', async () => { + const shard = await catalog.createShard({ + kind: 'vector', + shardKey: 'test-shard-1', + capacity: 100, + status: 'active', + }) + + expect(shard).toBeDefined() + expect(shard.shard_key).toBe('test-shard-1') + expect(shard.kind).toBe('vector') + expect(shard.capacity).toBe(100) + expect(shard.status).toBe('active') + }) + + it('should be idempotent when creating the same shard twice', async () => { + const shard1 = await catalog.createShard({ + kind: 'vector', + shardKey: 'test-shard-1', + capacity: 100, + }) + + const shard2 = await catalog.createShard({ + kind: 'vector', + shardKey: 'test-shard-1', + capacity: 100, + }) + + expect(shard1.id).toBe(shard2.id) + expect(shard1.shard_key).toBe(shard2.shard_key) + }) + + it('should create multiple shards in batch', async () => { + const shards = await catalog.createShards([ + { kind: 'vector', shardKey: 'shard-1', capacity: 100 }, + { kind: 'vector', shardKey: 'shard-2', capacity: 200 }, + { kind: 'vector', shardKey: 'shard-3', capacity: 300 }, + ]) + + expect(shards).toHaveLength(3) + expect(shards[0].shard_key).toBe('shard-1') + expect(shards[1].shard_key).toBe('shard-2') + expect(shards[2].shard_key).toBe('shard-3') + }) + + it('should update shard status', async () => { + const shard = await catalog.createShard({ + kind: 'vector', + shardKey: 'test-shard-1', + capacity: 100, + }) + + await catalog.setShardStatus(shard.id, 'draining') + + const store = storeFactory.autocommit() + const shards = await store.listActiveShards('vector') + expect(shards).toHaveLength(0) // draining shards are not active + }) + + it('should get shard stats', async () => { + await catalog.createShard({ + kind: 'vector', + shardKey: 'test-shard-1', + capacity: 100, + }) + + const stats = await catalog.shardStats('vector') + + expect(stats).toHaveLength(1) + expect(stats[0].shardKey).toBe('test-shard-1') + expect(stats[0].capacity).toBe(100) + expect(stats[0].used).toBe(0) + expect(stats[0].free).toBe(100) + }) + }) + + describe('Reservation Flow', () => { + beforeEach(async () => { + await catalog.createShard({ + kind: 'vector', + shardKey: 'test-shard-1', + capacity: 100, + }) + }) + + it('should reserve a slot successfully', async () => { + const reservation = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + expect(reservation).toBeDefined() + expect(reservation.reservationId).toBeDefined() + expect(reservation.shardKey).toBe('test-shard-1') + expect(reservation.slotNo).toBe(0) + expect(reservation.leaseExpiresAt).toBeDefined() + }) + + it('should be idempotent - return existing reservation', async () => { + const res1 = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + const res2 = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + expect(res1.reservationId).toBe(res2.reservationId) + expect(res1.slotNo).toBe(res2.slotNo) + }) + + it('should confirm a reservation', async () => { + const reservation = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + await catalog.confirm(reservation.reservationId, { + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + // Check that slot is now confirmed + const slots = await db('shard_slots').where({ slot_no: reservation.slotNo }).first() + + expect(slots.resource_id).toBe('vector::bucket-1::index-1') + }) + + it('should cancel a reservation', async () => { + const reservation = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + await catalog.cancel(reservation.reservationId) + + const resv = await db('shard_reservation').where({ id: reservation.reservationId }).first() + + expect(resv.status).toBe('cancelled') + }) + + it('should throw error when confirming non-existent reservation', async () => { + await expect( + catalog.confirm(randomUUID(), { + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + ).rejects.toThrow(ReservationNotFoundError) + }) + + it('should throw error when confirming expired reservation', async () => { + const reservation = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + leaseMs: 1, // 1ms lease + }) + + // Wait for lease to expire + await new Promise((resolve) => setTimeout(resolve, 10)) + + await expect( + catalog.confirm(reservation.reservationId, { + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + ).rejects.toThrow(ExpiredReservationError) + }) + + it('should free a slot by location', async () => { + const reservation = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + await catalog.confirm(reservation.reservationId, { + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + await catalog.freeByLocation(reservation.shardId, reservation.slotNo) + + const slot = await db('shard_slots').where({ slot_no: reservation.slotNo }).first() + + expect(slot.resource_id).toBeNull() + expect(slot.tenant_id).toBeNull() + }) + + it('should delete cancelled reservation and reuse slot', async () => { + const res1 = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + await catalog.cancel(res1.reservationId) + + // Try to reserve again with same logical key + const res2 = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + expect(res2.reservationId).not.toBe(res1.reservationId) + expect(res2.slotNo).toBe(res1.slotNo) // Same slot reused + }) + + it('should handle UniqueViolationError and return existing reservation', async () => { + // This test simulates the race condition where: + // 1. Process A checks for existing reservation (findReservationByKindKey) - finds nothing + // 2. Process B inserts a reservation for the same logical key + // 3. Process A tries to insert - gets UniqueViolationError on (kind, logical_key) + // 4. Process A catches the error, queries again, and returns the reservation from Process B + + // To simulate this, we'll use two concurrent reserve calls + // Due to advisory locks, only one will proceed at a time, but there's still a tiny + // window for race conditions. We'll make this more deterministic by: + // 1. Starting two concurrent reserve operations + // 2. One will succeed and insert the reservation + // 3. The other might hit UniqueViolationError if it checked before the first inserted + // but tries to insert after + + // Start two concurrent reservations for the same logical resource + const promises = [ + catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-race', + }), + catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-race', + }), + ] + + const results = await Promise.all(promises) + + // Both should succeed and return the same reservation (idempotent) + expect(results[0].reservationId).toBe(results[1].reservationId) + expect(results[0].slotNo).toBe(results[1].slotNo) + expect(results[0].shardKey).toBe(results[1].shardKey) + + // Verify only one reservation was created + const reservations = await db('shard_reservation') + .where({ kind: 'vector', resource_id: 'vector::bucket-1::index-race' }) + .select('*') + + expect(reservations).toHaveLength(1) + }) + }) + + describe('Slot Allocation', () => { + beforeEach(async () => { + await catalog.createShard({ + kind: 'vector', + shardKey: 'test-shard-1', + capacity: 5, + }) + }) + + it('should allocate sequential slot numbers', async () => { + const reservations = [] + for (let i = 0; i < 5; i++) { + const res = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: `index-${i}`, + }) + reservations.push(res) + } + + expect(reservations[0].slotNo).toBe(0) + expect(reservations[1].slotNo).toBe(1) + expect(reservations[2].slotNo).toBe(2) + expect(reservations[3].slotNo).toBe(3) + expect(reservations[4].slotNo).toBe(4) + }) + + it('should reuse freed slots before minting new ones', async () => { + const res1 = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + await catalog.confirm(res1.reservationId, { + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + // Free slot 0 + await catalog.freeByLocation(res1.shardId, res1.slotNo) + + // Reserve another - should reuse slot 0 + const res2 = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-2', + }) + + expect(res2.slotNo).toBe(0) + }) + + it('should throw error when shard is at capacity', async () => { + // Reserve all 5 slots + for (let i = 0; i < 5; i++) { + await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: `index-${i}`, + }) + } + + // Try to reserve one more + await expect( + catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-6', + }) + ).rejects.toThrow(NoActiveShardError) + }) + + it('should track tenant_id in slots', async () => { + const res = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + const slot = await db('shard_slots').where({ slot_no: res.slotNo }).first() + + expect(slot.tenant_id).toBe('tenant-1') + }) + }) + + describe('Lease Expiration', () => { + beforeEach(async () => { + await catalog.createShard({ + kind: 'vector', + shardKey: 'test-shard-1', + capacity: 100, + }) + }) + + it('should expire leases past their expiry time', async () => { + await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + leaseMs: 1, // 1ms lease + }) + + // Wait for lease to expire + await new Promise((resolve) => setTimeout(resolve, 10)) + + const expired = await catalog.expireLeases() + + expect(expired).toBe(1) + + // Check reservation is marked expired + const resv = await db('shard_reservation').first() + expect(resv.status).toBe('expired') + }) + + it('should allow reusing slot after lease expiration', async () => { + const res1 = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + leaseMs: 1, + }) + + await new Promise((resolve) => setTimeout(resolve, 10)) + await catalog.expireLeases() + + // Reserve with different logical key should reuse the slot + const res2 = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-2', + }) + + expect(res2.slotNo).toBe(res1.slotNo) + }) + + it('should allow reserving a slot after one slot is already expired', async () => { + const res1 = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + leaseMs: 1, + }) + + await new Promise((resolve) => setTimeout(resolve, 10)) + + // Reserve with different logical key should reuse the slot + const res2 = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-2', + }) + + expect(res2.slotNo).toBe(res1.slotNo) + }) + }) + + describe('Shard Selectors', () => { + describe('FillFirstShardSelector', () => { + beforeEach(async () => { + catalog = new ShardCatalog(storeFactory) + + // Create shards with different capacities + await catalog.createShards([ + { kind: 'vector', shardKey: 'shard-1', capacity: 10 }, + { kind: 'vector', shardKey: 'shard-2', capacity: 20 }, + { kind: 'vector', shardKey: 'shard-3', capacity: 30 }, + ]) + }) + + it('should fill shard-1 first (least free capacity)', async () => { + const res = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + expect(res.shardKey).toBe('shard-1') + }) + + it('should fill shards sequentially', async () => { + // Fill shard-1 completely (10 slots) + for (let i = 0; i < 10; i++) { + await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: `index-${i}`, + }) + } + + // Next reservation should go to shard-2 + const res = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-11', + }) + + expect(res.shardKey).toBe('shard-2') + }) + }) + }) + + describe('Concurrency Tests', () => { + beforeEach(async () => { + catalog = new ShardCatalog(storeFactory) + + await catalog.createShard({ + kind: 'vector', + shardKey: 'test-shard-1', + capacity: 100, + }) + }) + + it('should handle concurrent reservations without conflicts', async () => { + const promises = [] + for (let i = 0; i < 20; i++) { + promises.push( + catalog.reserve({ + kind: 'vector', + tenantId: `tenant-${i}`, + bucketName: 'bucket-1', + logicalName: `index-${i}`, + }) + ) + } + + const results = await Promise.all(promises) + + // All should succeed + expect(results).toHaveLength(20) + + // All should have unique slot numbers + const slotNumbers = results.map((r) => r.slotNo) + const uniqueSlots = new Set(slotNumbers) + expect(uniqueSlots.size).toBe(20) + }) + + it('should handle concurrent reservations on nearly-full shard', async () => { + // Create a shard with only 5 slots + await db('shard').delete() + await catalog.createShard({ + kind: 'vector', + shardKey: 'small-shard', + capacity: 5, + }) + + // Fill 3 slots + for (let i = 0; i < 3; i++) { + await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: `index-${i}`, + }) + } + + // Try to reserve 5 slots concurrently (only 2 should succeed) + const promises = [] + for (let i = 3; i < 8; i++) { + promises.push( + catalog + .reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: `index-${i}`, + }) + .catch((e) => e) + ) + } + + const results = await Promise.all(promises) + + const successes = results.filter((r) => r.reservationId) + const failures = results.filter((r) => r instanceof Error) + + expect(successes.length).toBe(2) + expect(failures.length).toBe(3) + }) + + it('should handle concurrent confirm operations', async () => { + const reservations: { + reservationId: string + shardId: string + shardKey: string + slotNo: number + leaseExpiresAt: string + }[] = [] + for (let i = 0; i < 10; i++) { + const res = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: `index-${i}`, + }) + reservations.push(res) + } + + // Confirm all concurrently + const confirmPromises = reservations.map((res) => + catalog.confirm(res.reservationId, { + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: `index-${reservations.indexOf(res)}`, + }) + ) + + await Promise.all(confirmPromises) + + // All should be confirmed + const slots = await db('shard_slots').where({ resource_id: null }).count('* as count') + expect(parseInt(slots[0].count as string)).toBe(0) + }) + + it('should handle race condition when selecting same nearly-full shard', async () => { + // Create two shards + await db('shard').delete() + await catalog.createShards([ + { kind: 'vector', shardKey: 'shard-1', capacity: 3 }, + { kind: 'vector', shardKey: 'shard-2', capacity: 100 }, + ]) + + // Fill shard-1 with 2 slots + for (let i = 0; i < 2; i++) { + await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: `index-${i}`, + }) + } + + // Try to reserve 5 slots concurrently + // Both processes might initially select shard-1 (has 1 slot free) + // One should get shard-1, others should fall back to shard-2 + const promises = [] + for (let i = 2; i < 7; i++) { + promises.push( + catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: `index-${i}`, + }) + ) + } + + const results = await Promise.all(promises) + + // All should succeed (no false negatives) + expect(results).toHaveLength(5) + + // Check shard distribution + const shard1Count = results.filter((r) => r.shardKey === 'shard-1').length + const shard2Count = results.filter((r) => r.shardKey === 'shard-2').length + + expect(shard1Count).toBe(1) // Only 1 slot available in shard-1 + expect(shard2Count).toBe(4) // Rest go to shard-2 + }) + }) + + describe('Edge Cases', () => { + it('should throw error when no shards exist', async () => { + await expect( + catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + ).rejects.toThrow(NoActiveShardError) + }) + + it('should throw error when all shards are disabled', async () => { + await catalog.createShard({ + kind: 'vector', + shardKey: 'test-shard-1', + capacity: 100, + status: 'disabled', + }) + + await expect( + catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + ).rejects.toThrow(NoActiveShardError) + }) + + it('should handle zero-capacity shard', async () => { + await catalog.createShard({ + kind: 'vector', + shardKey: 'zero-capacity', + capacity: 0, + }) + + await expect( + catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + ).rejects.toThrow(NoActiveShardError) + }) + + it('should isolate different resource kinds', async () => { + await catalog.createShards([ + { kind: 'vector', shardKey: 'vector-shard', capacity: 10 }, + { kind: 'iceberg-table', shardKey: 'iceberg-shard', capacity: 10 }, + ]) + + const vectorRes = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + const icebergRes = await catalog.reserve({ + kind: 'iceberg-table', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'table-1', + }) + + expect(vectorRes.shardKey).toBe('vector-shard') + expect(icebergRes.shardKey).toBe('iceberg-shard') + }) + + it('should find shard by resource id', async () => { + await catalog.createShard({ + kind: 'vector', + shardKey: 'test-shard-1', + capacity: 100, + }) + + const res = await catalog.reserve({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + await catalog.confirm(res.reservationId, { + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + const foundShard = await catalog.findShardByResourceId({ + kind: 'vector', + tenantId: 'tenant-1', + bucketName: 'bucket-1', + logicalName: 'index-1', + }) + + expect(foundShard?.shard_key).toBe('test-shard-1') + }) + }) +}) + +describe('SingleShard', () => { + it('returns shard stats in the canonical array shape', async () => { + const sharder = new SingleShard({ + shardKey: 'single-shard-key', + capacity: 25, + }) + + await expect(sharder.shardStats()).resolves.toEqual([ + { + shardId: '1', + shardKey: 'single-shard-key', + capacity: 25, + used: -1, + free: -1, + }, + ]) + }) +}) diff --git a/src/test/signature-v4-stream.test.ts b/src/test/signature-v4-stream.test.ts deleted file mode 100644 index 42cc1123a..000000000 --- a/src/test/signature-v4-stream.test.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { - ChunkSignatureV4Parser, - ChunkSignatureParserOptions, -} from '@storage/protocols/s3/signature-v4-stream' -import crypto from 'crypto' -import { Buffer } from 'buffer' - -describe('ChunkSignatureV4Parser', () => { - const makeParser = (opts: Partial = {}) => { - const defaultOpts: ChunkSignatureParserOptions = { - streamingAlgorithm: 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD', - maxChunkSize: 1024, - ...opts, - } - return new ChunkSignatureV4Parser(defaultOpts) - } - - test('constructor throws on invalid algorithm', () => { - expect( - () => new ChunkSignatureV4Parser({ streamingAlgorithm: 'INVALID' as any, maxChunkSize: 1 }) - ).toThrow(/Invalid streaming algorithm/) - }) - - test('parseHeaderLine accepts signed header and rejects malformed signature', () => { - const parser = makeParser() - const parse = (parser as any).parseHeaderLine.bind(parser) - const validSig = 'a'.repeat(64) - const { size, signature } = parse(`5;chunk-signature=${validSig}`) - expect(size).toBe(5) - expect(signature).toBe(validSig) - expect(() => parse('5;chunk-signature=xyz')).toThrow(/Invalid header/) - }) - - test('parseHeaderLine rejects invalid chunk size', () => { - const parser = makeParser() - const parse = (parser as any).parseHeaderLine.bind(parser) - expect(() => parse('zz;chunk-signature=' + 'a'.repeat(64))).toThrow(/Invalid header/) - expect(() => parse('zz')).toThrow(/Invalid chunk size/) - }) - - test('missing signature for signed algorithm emits error', () => { - const parser = makeParser({ maxChunkSize: 10 }) - return new Promise((resolve) => { - parser.on('error', (err) => { - expect(err).toBeInstanceOf(Error) - expect(err.message).toMatch(/Missing chunk signature/) - resolve() - }) - parser.write('5\r\n') - }) - }) - - test('header exceeding maxHeaderLength emits error', () => { - const parser = makeParser({ maxHeaderLength: 2 }) - return new Promise((resolve) => { - parser.on('error', (err) => { - expect(err.message).toMatch(/Header exceeds 2 bytes/) - resolve() - }) - parser.write('abc') - }) - }) - - test('chunk size exceeds maxChunkSize emits error', () => { - const parser = makeParser({ maxChunkSize: 1 }) - const sig = 'f'.repeat(64) - return new Promise((resolve) => { - parser.on('error', (err) => { - expect(err.message).toMatch(/Chunk size exceeds 1 bytes/) - resolve() - }) - parser.write(`2;chunk-signature=${sig}\r\n`) - }) - }) - - test('missing CRLF after chunk data emits error', () => { - const sig = '0'.repeat(64) - const parser = makeParser({ maxChunkSize: 10 }) - return new Promise((resolve) => { - parser.on('error', (err) => { - expect(err.message).toMatch(/Missing CRLF after chunk data/) - resolve() - }) - parser.write(`1;chunk-signature=${sig}\r\n`) - parser.write('a') - // write invalid footer prefix to trigger error - parser.write('xx') - }) - }) - - test('emits signatureReadyForVerification and data for single signed chunk', () => { - const sig = '0'.repeat(64) - const parser = makeParser({ maxChunkSize: 10 }) - const dataChunks: Buffer[] = [] - const sigEvents: Array<{ sig: string; size: number; hash: string; prev: string | undefined }> = - [] - - parser.on('data', (chunk: Buffer) => dataChunks.push(chunk)) - parser.on('signatureReadyForVerification', (sigVal, size, hash, prev) => { - sigEvents.push({ sig: sigVal, size, hash, prev }) - }) - - const header = `5;chunk-signature=${sig}\r\n` - const payload = 'hello' - const footer = '\r\n' - const endChunk = `0;chunk-signature=${sig}\r\n\r\n` - - parser.end(header + payload + footer + endChunk) - - return new Promise((resolve) => { - parser.on('end', () => { - expect(Buffer.concat(dataChunks).toString()).toBe('hello') - // only one signature event for the data chunk - expect(sigEvents).toHaveLength(1) - expect(sigEvents[0].size).toBe(5) - const expectedHash = crypto.createHash('sha256').update(payload).digest('hex') - expect(sigEvents[0].hash).toBe(expectedHash) - expect(sigEvents[0].sig).toBe(sig) - expect(sigEvents[0].prev).toBeUndefined() - resolve() - }) - }) - }) - - test('supports unsigned payload algorithm and emits trailer', () => { - const trailerKey = 'x-amz-meta-foo' - const trailerValue = 'bar' - const opts: ChunkSignatureParserOptions = { - streamingAlgorithm: 'STREAMING-UNSIGNED-PAYLOAD-TRAILER', - maxChunkSize: 1024, - trailerHeaderNames: [trailerKey], - } - const parser = new ChunkSignatureV4Parser(opts) - const dataChunks: Buffer[] = [] - let trailerObj: Record | undefined - - parser.on('data', (chunk: Buffer) => dataChunks.push(chunk)) - parser.on('trailer', (t) => { - trailerObj = t - }) - - const header = `3\r\n` - const payload = 'hey' - const footer = '\r\n' - const endChunk = `0\r\n` - const trailerBlock = `${trailerKey}: ${trailerValue}\r\nother: ignore\r\n\r\n` - - parser.end(header + payload + footer + endChunk + trailerBlock) - - return new Promise((resolve) => { - parser.on('end', () => { - expect(Buffer.concat(dataChunks).toString()).toBe('hey') - expect(trailerObj).toEqual({ [trailerKey]: trailerValue }) - resolve() - }) - }) - }) - - test('dataRead exceeding maxChunkSize emits error', () => { - const sig = 'a'.repeat(64) - const parser = makeParser({ maxChunkSize: 1 }) - return new Promise((resolve) => { - parser.on('error', (err) => { - expect(err.message).toMatch(/Chunk size exceeds 1 bytes/) - resolve() - }) - parser.write(`2;chunk-signature=${sig}\r\n`) - parser.write('ab') - }) - }) - - test('honors custom signaturePattern', () => { - const parser = makeParser({ - streamingAlgorithm: 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD', - maxChunkSize: 10, - signaturePattern: /^[0-9]+$/, - }) - const parse = (parser as any).parseHeaderLine.bind(parser) - expect(parse('3;chunk-signature=123').signature).toBe('123') - expect(() => parse('3;chunk-signature=abc')).toThrow() - }) -}) diff --git a/src/test/tenant-jwks.test.ts b/src/test/tenant-jwks.test.ts index 3059f1747..318e28d62 100644 --- a/src/test/tenant-jwks.test.ts +++ b/src/test/tenant-jwks.test.ts @@ -1,4 +1,9 @@ -'use strict' +vi.hoisted(() => { + process.env.PG_QUEUE_ENABLE = 'true' + process.env.MULTI_TENANT = 'true' + process.env.IS_MULTITENANT = 'true' +}) + import { getConfig, mergeConfig } from '../config' const { multitenantDatabaseUrl } = getConfig() @@ -7,20 +12,32 @@ mergeConfig({ isMultitenant: true, }) +import { encrypt, signJWT } from '@internal/auth' +import { TENANTS_JWKS_UPDATE_CHANNEL } from '@internal/auth/jwks/channels' +import { UrlSigningJwkGenerator } from '@internal/auth/jwks/generator' +import { JWKSManagerStoreKnex } from '@internal/auth/jwks/store-knex' +import { TENANT_JWKS_CACHE_NAME } from '@internal/cache' +import { + deleteTenantConfig, + getJwtSecret, + jwksManager, + listenForTenantUpdate, +} from '@internal/database' +import { cacheRequestsTotal } from '@internal/monitoring/metrics' +import { PostgresPubSub } from '@internal/pubsub' import dotenv from 'dotenv' import * as migrate from '../internal/database/migrations/migrate' import { multitenantKnex } from '../internal/database/multitenant-db' import { adminApp, mockQueue } from './common' -import { jwksManager, getJwtSecret } from '@internal/database' -import { listenForTenantUpdate } from '@internal/database' -import { PostgresPubSub } from '@internal/pubsub' -import { UrlSigningJwkGenerator } from '@internal/auth/jwks/generator' -import { signJWT } from '@internal/auth' -import { JWKSManagerStoreKnex } from '@internal/auth/jwks/store-knex' import { createMockKnexReturning } from './mocks/knex-mock' +import { assertLogicalLookupMetrics } from './utils/cache-metrics' +import { mockCreateLruCache } from './utils/cache-mock' +import { waitForEventually } from './utils/promise' dotenv.config({ path: '.env.test' }) +// Keep helper-level waits short so helper errors surface first. +const TENANT_JWKS_HELPER_TIMEOUT_MS = 4000 const tenantId = 'abc123' const testJwks = { @@ -51,10 +68,55 @@ const testJwks = { const pubSub = new PostgresPubSub(multitenantDatabaseUrl!) +type DatabaseModule = typeof import('../internal/database') +type JwksModule = typeof import('../internal/auth/jwks') + +async function loadJwksModules( + maxItems: number +): Promise<{ databaseModule: DatabaseModule; jwksModule: JwksModule }> { + vi.resetModules() + + const configModule = await import('../config') + configModule.getConfig({ reload: true }) + configModule.mergeConfig({ + pgQueueEnable: true, + isMultitenant: true, + }) + + mockCreateLruCache({ max: maxItems }) + + return { + databaseModule: await import('../internal/database'), + jwksModule: await import('../internal/auth/jwks'), + } +} + // returns a promise that resolves the next time the jwk cache is invalidated -function createJwkConfigChangeAwaiter(): Promise { - return new Promise((resolve) => { - pubSub.subscriber.notifications.once('tenants_jwks_update', resolve) +function createJwkConfigChangeAwaiter( + expectedCacheKey = tenantId, + timeoutMs = TENANT_JWKS_HELPER_TIMEOUT_MS +): Promise { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + pubSub.subscriber.notifications.removeListener(TENANTS_JWKS_UPDATE_CHANNEL, onNotification) + reject( + new Error( + `Timed out after ${timeoutMs}ms waiting for ${TENANTS_JWKS_UPDATE_CHANNEL}:${expectedCacheKey}` + ) + ) + }, timeoutMs) + + const onNotification = (cacheKey: string) => { + if (cacheKey !== expectedCacheKey) { + return + } + + clearTimeout(timeout) + pubSub.subscriber.notifications.removeListener(TENANTS_JWKS_UPDATE_CHANNEL, onNotification) + resolve(cacheKey) + } + + pubSub.subscriber.notifications.on(TENANTS_JWKS_UPDATE_CHANNEL, onNotification) }) } @@ -62,7 +124,7 @@ beforeAll(async () => { await migrate.runMultitenantMigrations() await pubSub.start() await listenForTenantUpdate(pubSub) - jest.spyOn(migrate, 'runMigrationsOnTenant').mockResolvedValue() + vi.spyOn(migrate, 'runMigrationsOnTenant').mockResolvedValue() }) beforeEach(async () => { @@ -94,11 +156,31 @@ afterEach(async () => { }) afterAll(async () => { + await adminApp.close() await pubSub.close() await multitenantKnex.destroy() }) describe('Tenant jwks configs', () => { + test('JWK change awaiter ignores unrelated tenant notifications', async () => { + const expectedTenantId = 'expected-jwks-tenant' + const awaiter = createJwkConfigChangeAwaiter(expectedTenantId) + let resolved = false + + void awaiter.then(() => { + resolved = true + }) + + pubSub.subscriber.notifications.emit(TENANTS_JWKS_UPDATE_CHANNEL, 'other-jwks-tenant') + await new Promise((resolve) => setImmediate(resolve)) + + expect(resolved).toBe(false) + + pubSub.subscriber.notifications.emit(TENANTS_JWKS_UPDATE_CHANNEL, expectedTenantId) + + await expect(awaiter).resolves.toBe(expectedTenantId) + }) + test('Add jwk without jwk in payload', async () => { const response = await adminApp.inject({ method: 'POST', @@ -166,15 +248,20 @@ describe('Tenant jwks configs', () => { expect(data.kid).toBeTruthy() expect(data.kid.startsWith(kind)).toBe(true) - const cacheKey = await configAwaiter - expect(cacheKey).toBe(tenantId) + await expect(configAwaiter).resolves.toBe(tenantId) - const config = await jwksManager.getJwksTenantConfig(tenantId) + const config = await waitForEventually( + () => jwksManager.getJwksTenantConfig(tenantId), + (value) => value.keys.some((key) => key.kid === data.kid), + `tenant ${tenantId} JWKS to include ${data.kid}` + ) expect(config.keys.length - keysBefore.length).toBe(1) expect(config.keys.find((v) => v.kid === data.kid)).toBeTruthy() }) test(`Add ${type} jwk via tenant patch (legacy)`, async () => { + const secretBeforePatch = await getJwtSecret(tenantId) + const patchResponse = await adminApp.inject({ method: 'PATCH', url: `/tenants/${tenantId}`, @@ -187,9 +274,17 @@ describe('Tenant jwks configs', () => { }) expect(patchResponse.statusCode).toBe(204) - const { jwks } = await getJwtSecret(tenantId) + deleteTenantConfig(tenantId) + + const secretAfterPatch = await getJwtSecret(tenantId) + const secretAfterSecondRead = await getJwtSecret(tenantId) + const { jwks } = secretAfterPatch + expect(jwks.keys.length).toBe(2) expect(jwks.keys[1]).toEqual(jwk) + expect(secretAfterPatch.secret).toBe(secretBeforePatch.secret) + expect(secretAfterSecondRead.jwks).toBe(secretAfterPatch.jwks) + expect(secretAfterSecondRead.jwks.keys).toEqual(jwks.keys) }) test(`Add ${type} jwk with missing data`, async () => { @@ -238,10 +333,13 @@ describe('Tenant jwks configs', () => { let data = response.json<{ result: boolean }>() expect(data.result).toBe(true) - let cacheKey = await configAwaiter - expect(cacheKey).toBe(tenantId) + await expect(configAwaiter).resolves.toBe(tenantId) - config = await jwksManager.getJwksTenantConfig(tenantId) + config = await waitForEventually( + () => jwksManager.getJwksTenantConfig(tenantId), + (value) => value.keys.length === 0, + `tenant ${tenantId} JWKS to clear after deactivation` + ) expect(config.keys.length).toBe(0) configAwaiter = createJwkConfigChangeAwaiter() @@ -257,12 +355,15 @@ describe('Tenant jwks configs', () => { data = response.json<{ result: boolean }>() expect(data.result).toBe(true) - cacheKey = await configAwaiter - expect(cacheKey).toBe(tenantId) + await expect(configAwaiter).resolves.toBe(tenantId) - const config2 = await jwksManager.getJwksTenantConfig(tenantId) + const config2 = await waitForEventually( + () => jwksManager.getJwksTenantConfig(tenantId), + (value) => value.keys.some((key) => key.kid === kid), + `tenant ${tenantId} JWKS to restore ${kid}` + ) expect(config2.keys.length).toBe(1) - expect(config2.keys[0]).toMatchObject({ kid: kid }) + expect(config2.keys[0]).toMatchObject({ kid }) }) test('Update unknown jwk', async () => { @@ -280,7 +381,7 @@ describe('Tenant jwks configs', () => { }) test('Config always retrieves concurrent requests from cache', async () => { - const listActiveSpy = jest.spyOn(jwksManager['storage'], 'listActive') + const listActiveSpy = vi.spyOn(jwksManager['storage'], 'listActive') try { const results = await Promise.all([ jwksManager.getJwksTenantConfig(tenantId), @@ -294,6 +395,78 @@ describe('Tenant jwks configs', () => { } }) + test('Config evicts cold tenants from cache', async () => { + const tenantIds = ['jwks-cache-eviction-1', 'jwks-cache-eviction-2', 'jwks-cache-eviction-3'] + const encryptedJwk = { + id: 'cache-eviction', + kind: 'storage-url-signing-key', + content: encrypt(JSON.stringify({ kty: 'oct', k: 'bounded-cache-test-key' })), + } + + const { databaseModule, jwksModule } = await loadJwksModules(2) + const listActiveSpy = vi.spyOn(databaseModule.jwksManager['storage'], 'listActive') + + try { + listActiveSpy.mockImplementation(async () => { + return [encryptedJwk] + }) + + for (const tenantId of tenantIds) { + await databaseModule.jwksManager.getJwksTenantConfig(tenantId) + } + + expect(listActiveSpy).toHaveBeenCalledTimes(tenantIds.length) + + await databaseModule.jwksManager.getJwksTenantConfig(tenantIds[0]) + + expect(listActiveSpy).toHaveBeenCalledTimes(tenantIds.length + 1) + } finally { + tenantIds.forEach((tenantId) => { + jwksModule.deleteTenantJwksConfig(tenantId) + }) + vi.doUnmock('@internal/cache') + vi.resetModules() + listActiveSpy.mockRestore() + } + }) + + test('Config records one cache request per logical lookup', async () => { + const listActiveSpy = vi.spyOn(jwksManager['storage'], 'listActive') + const addSpy = vi.spyOn(cacheRequestsTotal, 'add') + const lookupTenantId = 'jwks-cache-metrics-lookup' + const encryptedJwk = { + id: 'cache-metrics', + kind: 'storage-url-signing-key', + content: encrypt(JSON.stringify({ kty: 'oct', k: 'metric-cache-test-key' })), + } + + const listActiveRequest = Promise.withResolvers>() + + try { + listActiveSpy.mockImplementation(() => listActiveRequest.promise) + + await assertLogicalLookupMetrics({ + addSpy, + backendCallSpy: listActiveSpy, + cacheName: TENANT_JWKS_CACHE_NAME, + startLookups: () => [ + jwksManager.getJwksTenantConfig(lookupTenantId), + jwksManager.getJwksTenantConfig(lookupTenantId), + jwksManager.getJwksTenantConfig(lookupTenantId), + ], + resolveBackend: () => listActiveRequest.resolve([encryptedJwk]), + assertCachedHit: async () => { + await expect(jwksManager.getJwksTenantConfig(lookupTenantId)).resolves.toMatchObject({ + keys: [expect.objectContaining({ kid: 'storage-url-signing-key_cache-metrics' })], + }) + }, + }) + } finally { + listActiveSpy.mockRestore() + addSpy.mockRestore() + } + }) + test('Generate all jwks status', async () => { const response = await adminApp.inject({ method: 'GET', @@ -349,7 +522,7 @@ describe('Tenant jwks configs', () => { }) test('Generate all jwks when already running', async () => { - const statusSpy = jest + const statusSpy = vi .spyOn(UrlSigningJwkGenerator, 'getGenerationStatus') .mockReturnValueOnce({ running: true, sent: 99 }) @@ -370,7 +543,7 @@ describe('Tenant jwks configs', () => { }) test('Ensure list tenants exits before yield if no items are returned', async () => { - const listTenantsSpy = jest + const listTenantsSpy = vi .spyOn(jwksManager['storage'], 'listTenantsWithoutKindPaginated') .mockResolvedValue([]) try { @@ -408,9 +581,15 @@ describe('Tenant jwks configs', () => { }, }) - await configAwaiter + await expect(configAwaiter).resolves.toBe(tenantId) - const secretWithoutJwk = await getJwtSecret(tenantId) + deleteTenantConfig(tenantId) + + const secretWithoutJwk = await waitForEventually( + () => getJwtSecret(tenantId), + (value) => value.urlSigningKey === value.secret && value.jwks.keys.length === 0, + `tenant ${tenantId} url signing fallback to jwtSecret` + ) expect(secretWithoutJwk.urlSigningKey).toBe(secretWithoutJwk.secret) expect(secretWithoutJwk.jwks.keys.length).toBe(0) }) @@ -441,4 +620,104 @@ describe('Tenant jwks configs', () => { const insert = storage.insert('tenant-id', 'encrypted', 'kind', true) await expect(insert).rejects.toThrow('failed to find existing jwk on idempotent insert') }) + + test('Roll url signing key', async () => { + const queueSendSpy = mockQueue().sendSpy + const queueSpyAwaiter = new Promise((resolve) => { + queueSendSpy.mockImplementationOnce((...args) => { + resolve(args) + }) + }) + try { + const response = await adminApp.inject({ + method: 'POST', + url: `/tenants/${tenantId}/jwks/url-signing/roll`, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + expect(response.statusCode).toBe(200) + const data = response.json<{ started: boolean }>() + expect(data.started).toBe(true) + + await queueSpyAwaiter + expect(queueSendSpy).toHaveBeenCalledTimes(1) + const [[callArg]] = queueSendSpy.mock.calls + expect(callArg).toMatchObject({ + data: { tenantId }, + name: 'tenants-jwks-roll-url-signing-key-v1', + }) + } finally { + queueSendSpy.mockRestore() + } + }) + + test('Roll url signing key when no key exists', async () => { + let configAwaiter = createJwkConfigChangeAwaiter() + + const config = await jwksManager.getJwksTenantConfig(tenantId) + expect(config.keys.length).toBe(1) + const { kid } = config.keys[0] + + await adminApp.inject({ + method: 'PUT', + url: `/tenants/${tenantId}/jwks/${kid}`, + payload: { active: false }, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + + await expect(configAwaiter).resolves.toBe(tenantId) + + const configBeforeRoll = await waitForEventually( + () => jwksManager.getJwksTenantConfig(tenantId), + (value) => value.keys.length === 0, + `tenant ${tenantId} JWKS to clear after deactivation` + ) + expect(configBeforeRoll.keys.length).toBe(0) + + configAwaiter = createJwkConfigChangeAwaiter() + const { oldKid, newKid } = await jwksManager.rollUrlSigningJwk(tenantId) + + expect(oldKid).toBeNull() + expect(newKid).toContain('storage-url-signing-key') + + await expect(configAwaiter).resolves.toBe(tenantId) + const configAfterRoll = await waitForEventually( + () => jwksManager.getJwksTenantConfig(tenantId), + (value) => value.keys.length === 1, + `tenant ${tenantId} JWKS to clear after deactivation` + ) + expect(configAfterRoll.keys.length).toBe(1) + expect(configAfterRoll.keys[0].kid).toBe(newKid) + }) + + test('Roll url signing key atomically replaces existing key', async () => { + const configBefore = await jwksManager.getJwksTenantConfig(tenantId) + expect(configBefore.keys.length).toBe(1) + const oldKid = configBefore.keys[0].kid + + const configAwaiter = createJwkConfigChangeAwaiter() + const { oldKid: returnedOldKid, newKid } = await jwksManager.rollUrlSigningJwk(tenantId) + + expect(returnedOldKid).toBe(oldKid) + expect(newKid).not.toBe(oldKid) + expect(newKid).toContain('storage-url-signing-key') + + await expect(configAwaiter).resolves.toBe(tenantId) + const configAfter = await waitForEventually( + () => jwksManager.getJwksTenantConfig(tenantId), + (value) => value.keys[0].kid !== oldKid, + `tenant ${tenantId} JWKS to clear after deactivation` + ) + + await jwksManager.getJwksTenantConfig(tenantId) + expect(configAfter.keys.length).toBe(1) + expect(configAfter.keys[0].kid).toBe(newKid) + + const activeKeys = await jwksManager['storage'].listActive(tenantId, 'storage-url-signing-key') + expect(activeKeys.length).toBe(1) + expect(activeKeys[0].id).toBe(newKid.split('_')[1]) + }) }) diff --git a/src/test/tenant-s3-credentials.test.ts b/src/test/tenant-s3-credentials.test.ts index ab74149f6..bad872623 100644 --- a/src/test/tenant-s3-credentials.test.ts +++ b/src/test/tenant-s3-credentials.test.ts @@ -1,4 +1,9 @@ -'use strict' +vi.hoisted(() => { + process.env.PG_QUEUE_ENABLE = 'true' + process.env.MULTI_TENANT = 'true' + process.env.IS_MULTITENANT = 'true' +}) + import { getConfig, mergeConfig } from '../config' const { multitenantDatabaseUrl } = getConfig() @@ -7,14 +12,18 @@ mergeConfig({ isMultitenant: true, }) +import { encrypt, signJWT } from '@internal/auth' +import { TENANT_S3_CREDENTIALS_CACHE_NAME } from '@internal/cache' +import { listenForTenantUpdate, s3CredentialsManager } from '@internal/database' +import { cacheRequestsTotal } from '@internal/monitoring/metrics' +import { PostgresPubSub } from '@internal/pubsub' import dotenv from 'dotenv' +import objectSizeOf from 'object-sizeof' import * as migrate from '../internal/database/migrations/migrate' import { multitenantKnex } from '../internal/database/multitenant-db' import { adminApp } from './common' -import { s3CredentialsManager } from '@internal/database' -import { listenForTenantUpdate } from '@internal/database' -import { PostgresPubSub } from '@internal/pubsub' -import { encrypt, signJWT } from '@internal/auth' +import { assertLogicalLookupMetrics } from './utils/cache-metrics' +import { mockCreateLruCache } from './utils/cache-mock' dotenv.config({ path: '.env.test' }) @@ -22,6 +31,28 @@ const tenantId = 'abc123s3' const pubSub = new PostgresPubSub(multitenantDatabaseUrl!) +type S3CredentialsManagerType = typeof s3CredentialsManager + +async function loadS3CredentialsManager(maxSizeBytes: number): Promise { + vi.resetModules() + + const configModule = await import('../config') + configModule.getConfig({ reload: true }) + configModule.mergeConfig({ + pgQueueEnable: true, + isMultitenant: true, + }) + + mockCreateLruCache({ maxSize: maxSizeBytes }) + + const managerModule = await import('../storage/protocols/s3/credentials/manager') + const storeModule = await import('../storage/protocols/s3/credentials/store-knex') + + return new managerModule.S3CredentialsManager( + new storeModule.S3CredentialsManagerStoreKnex(multitenantKnex) + ) as S3CredentialsManagerType +} + // returns a promise that resolves the next time the jwk cache is invalidated function createS3CredentialsChangeAwaiter(): Promise { return new Promise((resolve) => { @@ -33,7 +64,7 @@ beforeAll(async () => { await migrate.runMultitenantMigrations() await pubSub.start() await listenForTenantUpdate(pubSub) - jest.spyOn(migrate, 'runMigrationsOnTenant').mockResolvedValue() + vi.spyOn(migrate, 'runMigrationsOnTenant').mockResolvedValue() }) beforeEach(async () => { @@ -62,9 +93,11 @@ afterEach(async () => { apikey: process.env.ADMIN_API_KEYS, }, }) + vi.doUnmock('@internal/cache') }) afterAll(async () => { + await adminApp.close() await pubSub.close() await multitenantKnex.destroy() }) @@ -143,7 +176,7 @@ describe('Tenant S3 credentials', () => { }) test('Add s3 credential with claim', async () => { - const knexTableSpy = jest.spyOn(multitenantKnex, 'table') + const knexTableSpy = vi.spyOn(multitenantKnex, 'table') try { const claimKept = { some: 'other', @@ -304,7 +337,7 @@ describe('Tenant S3 credentials', () => { }) test('Config always retrieves concurrent requests from cache', async () => { - const getByKeySpy = jest.spyOn(s3CredentialsManager['storage'], 'getOneByAccessKey') + const getByKeySpy = vi.spyOn(s3CredentialsManager['storage'], 'getOneByAccessKey') try { const response = await adminApp.inject({ method: 'POST', @@ -331,7 +364,7 @@ describe('Tenant S3 credentials', () => { }) test('Ensure cache is cleared on delete', async () => { - const knexTableSpy = jest.spyOn(multitenantKnex, 'table') + const knexTableSpy = vi.spyOn(multitenantKnex, 'table') const claims = { issuer: `supabase.storage.${tenantId}`, role: 'service_role', @@ -400,7 +433,7 @@ describe('Tenant S3 credentials', () => { }) test('Ensure cache is cleared on update', async () => { - const knexTableSpy = jest.spyOn(multitenantKnex, 'table') + const knexTableSpy = vi.spyOn(multitenantKnex, 'table') const claims = { issuer: `supabase.storage.${tenantId}`, role: 'service_role', @@ -465,4 +498,90 @@ describe('Tenant S3 credentials', () => { knexTableSpy.mockRestore() } }) + + test('Config evicts oversized cold credentials from cache', async () => { + const credentialBlob = 'x'.repeat(256) + const templateCredential = { + accessKey: 'template-access-key', + secretKey: encrypt('secret-template-access-key'), + claims: { + issuer: `supabase.storage.${tenantId}`, + role: 'service_role', + blob: credentialBlob, + }, + } + const s3CredentialsManagerWithSmallCache = await loadS3CredentialsManager( + objectSizeOf(templateCredential) + 1 + ) + const getByKeySpy = vi.spyOn(s3CredentialsManagerWithSmallCache['storage'], 'getOneByAccessKey') + + try { + getByKeySpy.mockImplementation(async (requestTenantId, accessKey) => { + return { + accessKey, + secretKey: encrypt(`secret-${accessKey}`), + claims: { + issuer: `supabase.storage.${requestTenantId}`, + role: 'service_role', + blob: credentialBlob, + }, + } + }) + + await s3CredentialsManagerWithSmallCache.getS3CredentialsByAccessKey(tenantId, 'small-key-1') + await s3CredentialsManagerWithSmallCache.getS3CredentialsByAccessKey(tenantId, 'small-key-2') + + expect(getByKeySpy).toHaveBeenCalledTimes(2) + + await s3CredentialsManagerWithSmallCache.getS3CredentialsByAccessKey(tenantId, 'small-key-1') + + expect(getByKeySpy).toHaveBeenCalledTimes(3) + } finally { + getByKeySpy.mockRestore() + } + }) + + test('Config records one cache request per logical lookup', async () => { + const getByKeySpy = vi.spyOn(s3CredentialsManager['storage'], 'getOneByAccessKey') + const addSpy = vi.spyOn(cacheRequestsTotal, 'add') + const lookupTenantId = 's3-cache-metrics-lookup' + const lookupAccessKey = 's3-cache-metrics-access-key' + const credentials = { + accessKey: lookupAccessKey, + secretKey: encrypt('metric-secret'), + claims: { + issuer: `supabase.storage.${lookupTenantId}`, + role: 'service_role', + }, + } + + const credentialsLookup = Promise.withResolvers() + + try { + getByKeySpy.mockImplementation(() => credentialsLookup.promise) + + await assertLogicalLookupMetrics({ + addSpy, + backendCallSpy: getByKeySpy, + cacheName: TENANT_S3_CREDENTIALS_CACHE_NAME, + startLookups: () => [ + s3CredentialsManager.getS3CredentialsByAccessKey(lookupTenantId, lookupAccessKey), + s3CredentialsManager.getS3CredentialsByAccessKey(lookupTenantId, lookupAccessKey), + s3CredentialsManager.getS3CredentialsByAccessKey(lookupTenantId, lookupAccessKey), + ], + resolveBackend: () => credentialsLookup.resolve(credentials), + assertCachedHit: async () => { + await expect( + s3CredentialsManager.getS3CredentialsByAccessKey(lookupTenantId, lookupAccessKey) + ).resolves.toMatchObject({ + accessKey: lookupAccessKey, + secretKey: 'metric-secret', + }) + }, + }) + } finally { + getByKeySpy.mockRestore() + addSpy.mockRestore() + } + }) }) diff --git a/src/test/tenant.test.ts b/src/test/tenant.test.ts index f1da239fc..619e6c72e 100644 --- a/src/test/tenant.test.ts +++ b/src/test/tenant.test.ts @@ -1,20 +1,26 @@ -'use strict' -import dotenv from 'dotenv' -import * as migrate from '../internal/database/migrations/migrate' -import { multitenantKnex } from '../internal/database/multitenant-db' -import { adminApp } from './common' +import { encrypt, signJWT } from '@internal/auth' +import { TENANT_CONFIG_CACHE_NAME } from '@internal/cache' +import { jwksManager } from '@internal/database' +import { DBMigration } from '@internal/database/migrations' import { + deleteTenantConfig, getFeatures, getFileSizeLimit, getServiceKey, getTenantConfig, } from '@internal/database/tenant' -import { signJWT } from '@internal/auth' -import { DBMigration } from '@internal/database/migrations' +import { cacheRequestsTotal } from '@internal/monitoring/metrics' +import dotenv from 'dotenv' +import * as migrate from '../internal/database/migrations/migrate' +import { multitenantKnex } from '../internal/database/multitenant-db' +import { adminApp } from './common' +import { assertLogicalLookupMetrics } from './utils/cache-metrics' +import { mockCreateLruCache } from './utils/cache-mock' dotenv.config({ path: '.env.test' }) const serviceKeyPayload = { abc: 123 } +const testTenantIds = ['abc', 'cache-test-abc'] as const const migrationVersion = Object.entries(DBMigration).sort(([_, a], [__, b]) => b - a)[0][0] @@ -47,11 +53,16 @@ const payload = { enabled: false, }, icebergCatalog: { - enabled: false, + enabled: true, maxCatalogs: 2, maxNamespaces: 10, maxTables: 10, }, + vectorBuckets: { + enabled: true, + maxBuckets: 2, + maxIndexes: 10, + }, }, disableEvents: null, } @@ -85,32 +96,64 @@ const payload2 = { enabled: true, }, icebergCatalog: { - enabled: false, + enabled: true, maxCatalogs: 2, maxNamespaces: 10, maxTables: 10, }, + vectorBuckets: { + enabled: true, + maxBuckets: 2, + maxIndexes: 10, + }, }, disableEvents: null, } +type TenantModule = typeof import('../internal/database/tenant') +type MultitenantDbModule = typeof import('../internal/database/multitenant-db') +type TenantQueryBuilder = ReturnType<(typeof multitenantKnex)['table']> + +async function loadTenantModule( + maxItems: number +): Promise<{ tenantModule: TenantModule; multitenantDbModule: MultitenantDbModule }> { + vi.resetModules() + mockCreateLruCache({ max: maxItems }) + + return { + tenantModule: await import('../internal/database/tenant'), + multitenantDbModule: await import('../internal/database/multitenant-db'), + } +} + beforeAll(async () => { await migrate.runMultitenantMigrations() - jest.spyOn(migrate, 'runMigrationsOnTenant').mockResolvedValue() + vi.spyOn(migrate, 'runMigrationsOnTenant').mockResolvedValue() payload.serviceKey = await signJWT(serviceKeyPayload, payload.jwtSecret, 100) }) +async function cleanupTestTenants() { + for (const tenantId of testTenantIds) { + await adminApp.inject({ + method: 'DELETE', + url: `/tenants/${tenantId}`, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + } +} + +beforeEach(async () => { + await cleanupTestTenants() +}) + afterEach(async () => { - await adminApp.inject({ - method: 'DELETE', - url: '/tenants/abc', - headers: { - apikey: process.env.ADMIN_API_KEYS, - }, - }) + await cleanupTestTenants() }) afterAll(async () => { + await adminApp.close() await multitenantKnex.destroy() }) @@ -179,6 +222,40 @@ describe('Tenant configs', () => { await expect(getFeatures('abc')).resolves.toEqual(payload.features) }) + test('Create tenant config preserves disableEvents and image transformation maxResolution', async () => { + const createPayload = { + ...payload, + disableEvents: ['ObjectCreated:*', 'ObjectRemoved:*'], + features: { + ...payload.features, + imageTransformation: { + ...payload.features.imageTransformation, + maxResolution: 1024, + }, + }, + } + + const createResponse = await adminApp.inject({ + method: 'POST', + url: `/tenants/abc`, + payload: createPayload, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + expect(createResponse.statusCode).toBe(201) + + const getResponse = await adminApp.inject({ + method: 'GET', + url: `/tenants/abc`, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + expect(getResponse.statusCode).toBe(200) + expect(JSON.parse(getResponse.body)).toEqual(createPayload) + }) + test('Insert tenant config without required properties', async () => { const response = await adminApp.inject({ method: 'POST', @@ -212,6 +289,36 @@ describe('Tenant configs', () => { expect(secondInsertResponse.statusCode).toBe(500) }) + test('Create tenant config rolls back when jwk generation fails', async () => { + const generateUrlSigningJwkSpy = vi + .spyOn(jwksManager, 'generateUrlSigningJwk') + .mockRejectedValueOnce(new Error('jwk insert failed')) + const runMigrationsOnTenantMock = vi.mocked(migrate.runMigrationsOnTenant) + runMigrationsOnTenantMock.mockClear() + + try { + const response = await adminApp.inject({ + method: 'POST', + url: `/tenants/abc`, + payload, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + + expect(response.statusCode).toBe(500) + expect(generateUrlSigningJwkSpy).toHaveBeenCalledWith('abc', expect.anything()) + expect(runMigrationsOnTenantMock).not.toHaveBeenCalled() + + await expect(multitenantKnex('tenants').where({ id: 'abc' }).first()).resolves.toBeUndefined() + await expect( + multitenantKnex('tenants_jwks').where({ tenant_id: 'abc' }).select('id') + ).resolves.toEqual([]) + } finally { + generateUrlSigningJwkSpy.mockRestore() + } + }) + test('Update tenant config', async () => { await adminApp.inject({ method: 'POST', @@ -241,6 +348,63 @@ describe('Tenant configs', () => { expect(getResponseJSON).toEqual(payload2) }) + test('Update tenant config keeps changes when tenant migrations fail', async () => { + await adminApp.inject({ + method: 'POST', + url: `/tenants/abc`, + payload, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + + const runMigrationsOnTenantMock = vi.mocked(migrate.runMigrationsOnTenant) + const updateTenantMigrationsStateSpy = vi.spyOn(migrate, 'updateTenantMigrationsState') + const addTenantSpy = vi + .spyOn(migrate.progressiveMigrations, 'addTenant') + .mockImplementation(() => undefined) + + runMigrationsOnTenantMock.mockClear() + updateTenantMigrationsStateSpy.mockClear() + + try { + runMigrationsOnTenantMock.mockRejectedValueOnce(new Error('migration failed')) + + const patchResponse = await adminApp.inject({ + method: 'PATCH', + url: `/tenants/abc`, + payload: payload2, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + + expect(patchResponse.statusCode).toBe(204) + expect(runMigrationsOnTenantMock).toHaveBeenCalledWith( + expect.objectContaining({ + databaseUrl: payload2.databaseUrl, + tenantId: 'abc', + }) + ) + expect(updateTenantMigrationsStateSpy).not.toHaveBeenCalled() + expect(addTenantSpy).toHaveBeenCalledWith('abc') + + const getResponse = await adminApp.inject({ + method: 'GET', + url: `/tenants/abc`, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + + expect(getResponse.statusCode).toBe(200) + expect(JSON.parse(getResponse.body)).toEqual(payload2) + } finally { + addTenantSpy.mockRestore() + updateTenantMigrationsStateSpy.mockRestore() + } + }) + test('Update tenant config partially', async () => { await adminApp.inject({ method: 'POST', @@ -270,6 +434,167 @@ describe('Tenant configs', () => { expect(getResponseJSON).toEqual({ ...payload, fileSizeLimit: 2 }) }) + test('Update tenant databasePoolUrl to null', async () => { + await adminApp.inject({ + method: 'POST', + url: `/tenants/abc`, + payload, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + + const patchResponse = await adminApp.inject({ + method: 'PATCH', + url: `/tenants/abc`, + payload: { databasePoolUrl: null }, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + expect(patchResponse.statusCode).toBe(204) + + const getResponse = await adminApp.inject({ + method: 'GET', + url: `/tenants/abc`, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + expect(getResponse.statusCode).toBe(200) + + const getResponseJSON = JSON.parse(getResponse.body) + expect(getResponseJSON).toEqual({ ...payload, databasePoolUrl: null }) + expect(getResponseJSON.databasePoolUrl).toBeNull() + }) + + test('Upsert tenant config updates iceberg/vector limits when enabled is omitted', async () => { + await adminApp.inject({ + method: 'POST', + url: `/tenants/abc`, + payload, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + + const updatedValue = 999 + + const putPayloadWithoutFeatureEnabled = { + ...payload, + features: { + ...payload.features, + icebergCatalog: { + maxCatalogs: updatedValue, + maxNamespaces: updatedValue, + maxTables: updatedValue, + }, + vectorBuckets: { + maxBuckets: updatedValue, + maxIndexes: updatedValue, + }, + }, + } + + const putResponse = await adminApp.inject({ + method: 'PUT', + url: `/tenants/abc`, + payload: putPayloadWithoutFeatureEnabled, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + expect(putResponse.statusCode).toBe(204) + + const getResponse = await adminApp.inject({ + method: 'GET', + url: `/tenants/abc`, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + expect(getResponse.statusCode).toBe(200) + + const getResponseJSON = JSON.parse(getResponse.body) + expect(getResponseJSON.features.icebergCatalog).toEqual({ + enabled: payload.features.icebergCatalog.enabled, + maxCatalogs: updatedValue, + maxNamespaces: updatedValue, + maxTables: updatedValue, + }) + expect(getResponseJSON.features.vectorBuckets).toEqual({ + enabled: payload.features.vectorBuckets.enabled, + maxBuckets: updatedValue, + maxIndexes: updatedValue, + }) + }) + + test('Upsert tenant config updates disableEvents and image transformation maxResolution', async () => { + const firstPayload = { + ...payload, + disableEvents: ['ObjectCreated:*', 'ObjectRemoved:*'], + features: { + ...payload.features, + imageTransformation: { + ...payload.features.imageTransformation, + maxResolution: 1024, + }, + }, + } + + const firstPutResponse = await adminApp.inject({ + method: 'PUT', + url: `/tenants/abc`, + payload: firstPayload, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + expect(firstPutResponse.statusCode).toBe(204) + + const firstGetResponse = await adminApp.inject({ + method: 'GET', + url: `/tenants/abc`, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + expect(firstGetResponse.statusCode).toBe(200) + expect(JSON.parse(firstGetResponse.body)).toEqual(firstPayload) + + const secondPayload = { + ...payload2, + disableEvents: ['ObjectCreated:*'], + features: { + ...payload2.features, + imageTransformation: { + ...payload2.features.imageTransformation, + maxResolution: 2048, + }, + }, + } + + const secondPutResponse = await adminApp.inject({ + method: 'PUT', + url: `/tenants/abc`, + payload: secondPayload, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + expect(secondPutResponse.statusCode).toBe(204) + + const secondGetResponse = await adminApp.inject({ + method: 'GET', + url: `/tenants/abc`, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + expect(secondGetResponse.statusCode).toBe(200) + expect(JSON.parse(secondGetResponse.body)).toEqual(secondPayload) + }) + test('Upsert tenant config', async () => { const firstPutResponse = await adminApp.inject({ method: 'PUT', @@ -337,28 +662,26 @@ describe('Tenant configs', () => { }) test('Get tenant config with invalid tenant id expected error', async () => { - await expect(getTenantConfig('')).rejects.toThrowError('Invalid tenant id') + await expect(getTenantConfig('')).rejects.toThrow('Invalid tenant id') }) test('Get tenant config with unknown tenant id expected error', async () => { - await expect(getTenantConfig('zzz')).rejects.toThrowError( - 'Missing tenant config for tenant zzz' - ) + await expect(getTenantConfig('zzz')).rejects.toThrow('Missing tenant config for tenant zzz') }) test('Get tenant config always retrieves concurrent requests from cache', async () => { - const knexTableSpy = jest.spyOn(multitenantKnex, 'table') - try { - const tenantId = 'cache-test-abc' - await adminApp.inject({ - method: 'POST', - url: `/tenants/${tenantId}`, - payload, - headers: { - apikey: process.env.ADMIN_API_KEYS, - }, - }) + const tenantId = 'cache-test-abc' + await adminApp.inject({ + method: 'POST', + url: `/tenants/${tenantId}`, + payload, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + const knexTableSpy = vi.spyOn(multitenantKnex, 'table') + try { await getTenantConfig(tenantId) expect(knexTableSpy).toHaveBeenCalledTimes(1) expect(knexTableSpy).toHaveBeenCalledWith('tenants') @@ -382,4 +705,126 @@ describe('Tenant configs', () => { knexTableSpy.mockRestore() } }) + + test('Get tenant config evicts cold tenants from cache', async () => { + const tenantIds = ['cache-eviction-1', 'cache-eviction-2', 'cache-eviction-3'] + const encryptedTenant = { + anon_key: encrypt('anon'), + database_url: encrypt('postgres://tenant'), + database_pool_mode: null, + file_size_limit: 1, + jwt_secret: encrypt('jwt-secret'), + jwks: null, + service_key: encrypt('service-key'), + feature_purge_cache: false, + feature_image_transformation: false, + feature_s3_protocol: false, + feature_iceberg_catalog: false, + feature_iceberg_catalog_max_catalogs: 0, + feature_iceberg_catalog_max_namespaces: 0, + feature_iceberg_catalog_max_tables: 0, + feature_vector_buckets: false, + feature_vector_buckets_max_buckets: 0, + feature_vector_buckets_max_indexes: 0, + image_transformation_max_resolution: null, + database_pool_url: null, + max_connections: null, + migrations_version: migrationVersion, + migrations_status: 'COMPLETED', + tracing_mode: null, + disable_events: null, + } + + const { tenantModule, multitenantDbModule } = await loadTenantModule(2) + const knexTableSpy = vi.spyOn(multitenantDbModule.multitenantKnex, 'table') + const queryBuilder = { + first: vi.fn().mockReturnThis(), + where: vi.fn().mockReturnThis(), + abortOnSignal: vi.fn().mockResolvedValue(encryptedTenant), + } + + try { + knexTableSpy.mockReturnValue(queryBuilder as unknown as TenantQueryBuilder) + + for (const tenantId of tenantIds) { + await tenantModule.getTenantConfig(tenantId) + } + + expect(knexTableSpy).toHaveBeenCalledTimes(tenantIds.length) + + await tenantModule.getTenantConfig(tenantIds[0]) + + expect(knexTableSpy).toHaveBeenCalledTimes(tenantIds.length + 1) + } finally { + tenantIds.forEach((tenantId) => { + tenantModule.deleteTenantConfig(tenantId) + }) + vi.doUnmock('@internal/cache') + vi.resetModules() + knexTableSpy.mockRestore() + } + }) + + test('Get tenant config records one cache request per logical lookup', async () => { + const knexTableSpy = vi.spyOn(multitenantKnex, 'table') + const addSpy = vi.spyOn(cacheRequestsTotal, 'add') + const tenantId = 'cache-metrics-lookup' + const encryptedTenant = { + anon_key: encrypt('anon'), + database_url: encrypt('postgres://tenant'), + database_pool_mode: null, + file_size_limit: 1, + jwt_secret: encrypt('jwt-secret'), + jwks: null, + service_key: encrypt('service-key'), + feature_purge_cache: false, + feature_image_transformation: false, + feature_s3_protocol: false, + feature_iceberg_catalog: false, + feature_iceberg_catalog_max_catalogs: 0, + feature_iceberg_catalog_max_namespaces: 0, + feature_iceberg_catalog_max_tables: 0, + feature_vector_buckets: false, + feature_vector_buckets_max_buckets: 0, + feature_vector_buckets_max_indexes: 0, + image_transformation_max_resolution: null, + database_pool_url: null, + max_connections: null, + migrations_version: migrationVersion, + migrations_status: 'COMPLETED', + tracing_mode: null, + disable_events: null, + } + + const tenantQuery = Promise.withResolvers() + const queryBuilder = { + first: vi.fn().mockReturnThis(), + where: vi.fn().mockReturnThis(), + abortOnSignal: vi.fn().mockImplementation(() => tenantQuery.promise), + } + + try { + knexTableSpy.mockReturnValue(queryBuilder as unknown as TenantQueryBuilder) + await assertLogicalLookupMetrics({ + addSpy, + backendCallSpy: queryBuilder.abortOnSignal, + cacheName: TENANT_CONFIG_CACHE_NAME, + startLookups: () => [ + getTenantConfig(tenantId), + getTenantConfig(tenantId), + getTenantConfig(tenantId), + ], + resolveBackend: () => tenantQuery.resolve(encryptedTenant), + assertCachedHit: async () => { + await expect(getTenantConfig(tenantId)).resolves.toMatchObject({ + databaseUrl: 'postgres://tenant', + }) + }, + }) + } finally { + deleteTenantConfig(tenantId) + knexTableSpy.mockRestore() + addSpy.mockRestore() + } + }) }) diff --git a/src/test/tus.test.ts b/src/test/tus.test.ts index aa2e92eb5..6c49e99b8 100644 --- a/src/test/tus.test.ts +++ b/src/test/tus.test.ts @@ -1,22 +1,22 @@ import dotenv from 'dotenv' import path from 'path' + dotenv.config({ path: path.resolve(__dirname, '..', '..', '.env.test') }) -import fs from 'fs' -import { FastifyInstance } from 'fastify' +import { CreateBucketCommand, S3Client } from '@aws-sdk/client-s3' +import { wait } from '@internal/concurrency' +import { getPostgresConnection, getServiceKeyUser } from '@internal/database' +import { logger } from '@internal/monitoring' +import { TenantLocation } from '@storage/locator' import { randomUUID } from 'crypto' +import { FastifyInstance } from 'fastify' +import fs from 'fs' import * as tus from 'tus-js-client' import { DetailedError } from 'tus-js-client' -import { CreateBucketCommand, S3Client } from '@aws-sdk/client-s3' - -import { logger } from '@internal/monitoring' -import { getServiceKeyUser, getPostgresConnection } from '@internal/database' -import { getConfig } from '../config' import app from '../app' +import { getConfig } from '../config' +import { backends, Storage, StorageKnexDB } from '../storage' import { checkBucketExists } from './common' -import { Storage, backends, StorageKnexDB } from '../storage' -import { wait } from '@internal/concurrency' -import { TenantLocation } from '@storage/locator' const { serviceKeyAsync, tenantId, storageS3Bucket, storageBackendType } = getConfig() const oneChunkFile = fs.createReadStream(path.resolve(__dirname, 'assets', 'sadcat.jpg')) @@ -61,7 +61,7 @@ describe('Tus multipart', () => { const pg = await getPostgresConnection({ superUser, user: superUser, - tenantId: tenantId, + tenantId, host: 'localhost', }) @@ -95,8 +95,8 @@ describe('Tus multipart', () => { 'x-upsert': 'true', }, metadata: { - bucketName: bucketName, - objectName: objectName, + bucketName, + objectName, contentType: 'image/jpeg', cacheControl: '3600', metadata: JSON.stringify({ @@ -104,7 +104,7 @@ describe('Tus multipart', () => { test2: 'test2', }), }, - onError: function (error) { + onError(error) { console.log('Failed because: ' + error) reject(error) }, @@ -124,7 +124,6 @@ describe('Tus multipart', () => { created_at: expect.any(Date), id: expect.any(String), last_accessed_at: expect.any(Date), - level: 1, metadata: { cacheControl: 'max-age=3600', contentLength: 29526, @@ -171,11 +170,11 @@ describe('Tus multipart', () => { }, metadata: { bucketName: 'doesn-exist', - objectName: objectName, + objectName, contentType: 'image/jpeg', cacheControl: '3600', }, - onError: function (error) { + onError(error) { console.log('Failed because: ' + error) reject(error) }, @@ -219,12 +218,12 @@ describe('Tus multipart', () => { 'x-upsert': 'true', }, metadata: { - bucketName: bucketName, - objectName: objectName, + bucketName, + objectName, contentType: 'image/jpeg', cacheControl: '3600', }, - onError: function (error) { + onError(error) { console.log('Failed because: ' + error) reject(error) }, @@ -270,8 +269,8 @@ describe('Tus multipart', () => { 'x-signature': signedUpload.token, }, metadata: { - bucketName: bucketName, - objectName: objectName, + bucketName, + objectName, contentType: 'image/jpeg', cacheControl: '3600', metadata: JSON.stringify({ @@ -279,7 +278,7 @@ describe('Tus multipart', () => { test3: 'test3', }), }, - onError: function (error) { + onError(error) { console.log('Failed because: ' + error) reject(error) }, @@ -298,7 +297,6 @@ describe('Tus multipart', () => { bucket_id: bucket.id, created_at: expect.any(Date), id: expect.any(String), - level: 1, last_accessed_at: expect.any(Date), metadata: { cacheControl: 'max-age=3600', @@ -344,12 +342,12 @@ describe('Tus multipart', () => { 'x-signature': signedUpload.token, }, metadata: { - bucketName: bucketName, - objectName: objectName, + bucketName, + objectName, contentType: 'image/jpeg', cacheControl: '3600', }, - onError: function (error) { + onError(error) { console.log('Failed because: ' + error) reject(error) }, @@ -369,7 +367,6 @@ describe('Tus multipart', () => { created_at: expect.any(Date), id: expect.any(String), last_accessed_at: expect.any(Date), - level: 1, metadata: { cacheControl: 'max-age=3600', contentLength: 29526, @@ -414,12 +411,12 @@ describe('Tus multipart', () => { 'x-signature': signedUpload.token, }, metadata: { - bucketName: bucketName, - objectName: objectName, + bucketName, + objectName, contentType: 'image/jpeg', cacheControl: '3600', }, - onError: function (error) { + onError(error) { console.log('Failed because: ' + error) reject(error) }, @@ -462,12 +459,12 @@ describe('Tus multipart', () => { 'x-signature': 'invalid-token', }, metadata: { - bucketName: bucketName, - objectName: objectName, + bucketName, + objectName, contentType: 'image/jpeg', cacheControl: '3600', }, - onError: function (error) { + onError(error) { console.log('Failed because: ' + error) reject(error) }, @@ -507,12 +504,12 @@ describe('Tus multipart', () => { onShouldRetry: () => false, uploadDataDuringCreation: false, metadata: { - bucketName: bucketName, - objectName: objectName, + bucketName, + objectName, contentType: 'image/jpeg', cacheControl: '3600', }, - onError: function (error) { + onError(error) { console.log('Failed because: ' + error) reject(error) }, diff --git a/src/test/uploader.test.ts b/src/test/uploader.test.ts new file mode 100644 index 000000000..ae2a2d988 --- /dev/null +++ b/src/test/uploader.test.ts @@ -0,0 +1,383 @@ +import { once } from 'events' +import { FastifyRequest } from 'fastify' +import { PassThrough, Readable } from 'stream' +import { ErrorCode, isStorageError, StorageBackendError } from '../internal/errors' +import { ObjectAdminDelete } from '../storage/events' +import { TenantLocation } from '../storage/locator' +import { fileUploadFromRequest, Uploader } from '../storage/uploader' + +type UploaderBackend = ConstructorParameters[0] +type UploaderDatabase = ConstructorParameters[1] +type CompleteUploadResult = Awaited> + +function createUploader( + backend: Partial & Pick, + db: Partial & + Pick +) { + return new Uploader( + backend as UploaderBackend, + db as UploaderDatabase, + new TenantLocation('test-bucket') + ) +} + +describe('fileUploadFromRequest', () => { + test('keeps multipart/form-data file size undefined even when the request content-length exceeds 5GB', async () => { + const file = Readable.from(['payload']) as Readable & { truncated: boolean } + file.truncated = false + + const requestFile = vi.fn().mockResolvedValue({ + file, + fields: { + cacheControl: { value: '3600' }, + contentType: { value: 'image/png' }, + metadata: { value: '{"source":"multipart"}' }, + }, + mimetype: 'application/octet-stream', + }) + + const upload = await fileUploadFromRequest( + { + headers: { + 'content-type': 'multipart/form-data; boundary=abc123', + 'content-length': String(5 * 1024 * 1024 * 1024 + 512), + }, + file: requestFile, + tenantId: 'stub-tenant', + } as unknown as FastifyRequest, + { + objectName: 'test.txt', + fileSizeLimit: 150, + } + ) + + expect(requestFile).toHaveBeenCalledWith({ limits: { fileSize: 150 } }) + expect(upload.body).toBe(file) + expect(upload.contentLength).toBeUndefined() + expect(upload.declaredContentLength).toBe(5 * 1024 * 1024 * 1024 + 512) + expect(upload.mimeType).toBe('image/png') + expect(upload.cacheControl).toBe('max-age=3600') + expect(upload.userMetadata).toEqual({ source: 'multipart' }) + expect(upload.isTruncated()).toBe(false) + + file.truncated = true + expect(upload.isTruncated()).toBe(true) + }) + + test('prefers x-amz-decoded-content-length for aws-chunked truncation checks', async () => { + const upload = await fileUploadFromRequest( + { + headers: { + 'content-type': 'application/octet-stream', + 'content-length': '177', + 'x-amz-decoded-content-length': '123', + }, + raw: Readable.from(['payload']), + streamingSignatureV4: {} as FastifyRequest['streamingSignatureV4'], + tenantId: 'stub-tenant', + } as unknown as FastifyRequest, + { + objectName: 'test.txt', + fileSizeLimit: 150, + } + ) + + expect(upload.contentLength).toBeUndefined() + expect(upload.declaredContentLength).toBe(123) + expect(upload.isTruncated()).toBe(false) + }) + + test('ignores x-amz-decoded-content-length outside aws-chunked S3 uploads and rejects oversized bodies', async () => { + try { + await fileUploadFromRequest( + { + headers: { + 'content-type': 'application/octet-stream', + 'content-length': '177', + 'x-amz-decoded-content-length': '123', + }, + raw: Readable.from(['payload']), + tenantId: 'stub-tenant', + } as unknown as FastifyRequest, + { + objectName: 'test.txt', + fileSizeLimit: 150, + } + ) + throw new Error('Expected fileUploadFromRequest to throw') + } catch (error) { + expect(isStorageError(ErrorCode.EntityTooLarge, error)).toBe(true) + } + }) + + test('rejects known-size binary uploads that already exceed the size limit', async () => { + const raw = new PassThrough() + + try { + await fileUploadFromRequest( + { + headers: { + 'content-type': 'application/octet-stream', + 'content-length': '177', + }, + raw, + tenantId: 'stub-tenant', + } as unknown as FastifyRequest, + { + objectName: 'test.txt', + fileSizeLimit: 150, + } + ) + throw new Error('Expected fileUploadFromRequest to throw') + } catch (error) { + expect(isStorageError(ErrorCode.EntityTooLarge, error)).toBe(true) + expect(raw.listenerCount('aborted')).toBe(0) + expect(raw.listenerCount('close')).toBe(0) + expect(raw.listenerCount('end')).toBe(0) + expect(raw.listenerCount('error')).toBe(0) + expect(raw.readableFlowing).not.toBe(true) + } + }) + + test('wraps binary request bodies so downstream stream failures do not destroy the raw request', async () => { + const raw = new PassThrough() + const upload = await fileUploadFromRequest( + { + headers: { + 'content-type': 'application/octet-stream', + 'content-length': '7', + }, + raw, + tenantId: 'stub-tenant', + } as unknown as FastifyRequest, + { + objectName: 'test.txt', + fileSizeLimit: 150, + } + ) + + expect(upload.body).not.toBe(raw) + expect(upload.contentLength).toBeUndefined() + expect(upload.declaredContentLength).toBe(7) + + const proxyError = once(upload.body, 'error') + upload.body.destroy(new Error('downstream failed')) + + const [error] = await proxyError + expect((error as Error).message).toBe('downstream failed') + expect(raw.destroyed).toBe(false) + }) + + test('cleans up raw request listeners after a successful proxied upload stream completes', async () => { + const raw = new PassThrough() + const upload = await fileUploadFromRequest( + { + headers: { + 'content-type': 'application/octet-stream', + 'content-length': '7', + }, + raw, + tenantId: 'stub-tenant', + } as unknown as FastifyRequest, + { + objectName: 'test.txt', + fileSizeLimit: 150, + } + ) + + const proxyClosed = once(upload.body, 'close') + upload.body.resume() + raw.end('payload') + await proxyClosed + + expect(raw.listenerCount('aborted')).toBe(0) + expect(raw.listenerCount('close')).toBe(0) + expect(raw.listenerCount('end')).toBe(0) + expect(raw.listenerCount('error')).toBe(0) + }) + + test('propagates raw request stream errors to the upload body proxy', async () => { + const raw = new PassThrough() + const upload = await fileUploadFromRequest( + { + headers: { + 'content-type': 'application/octet-stream', + 'content-length': '7', + }, + raw, + tenantId: 'stub-tenant', + } as unknown as FastifyRequest, + { + objectName: 'test.txt', + fileSizeLimit: 150, + } + ) + + const proxyError = once(upload.body, 'error') + const requestError = new Error('request stream failed') + raw.destroy(requestError) + + const [error] = await proxyError + expect(error).toBe(requestError) + expect(upload.body.destroyed).toBe(true) + }) + + test('destroys the upload body proxy when the raw request closes without EOF', async () => { + const raw = new PassThrough() + const upload = await fileUploadFromRequest( + { + headers: { + 'content-type': 'application/octet-stream', + 'content-length': '7', + }, + raw, + tenantId: 'stub-tenant', + } as unknown as FastifyRequest, + { + objectName: 'test.txt', + fileSizeLimit: 150, + } + ) + + const proxyError = once(upload.body, 'error') + raw.destroy() + + const [error] = await proxyError + expect((error as Error).message).toBe('Request stream closed before upload could complete') + expect(upload.body.destroyed).toBe(true) + }) + + test('rejects binary uploads when the raw request stream is already closed', async () => { + const raw = new PassThrough() + raw.destroy() + + try { + await fileUploadFromRequest( + { + headers: { + 'content-type': 'application/octet-stream', + 'content-length': '7', + }, + raw, + tenantId: 'stub-tenant', + } as unknown as FastifyRequest, + { + objectName: 'test.txt', + fileSizeLimit: 150, + } + ) + throw new Error('Expected fileUploadFromRequest to throw') + } catch (error) { + expect(isStorageError(ErrorCode.InvalidRequest, error)).toBe(true) + expect((error as Error).message).toBe('Request stream closed before upload could begin') + } + }) + + test('marks proxied upload failures to close the client connection after the response', async () => { + const raw = new PassThrough() + const file = await fileUploadFromRequest( + { + headers: { + 'content-type': 'application/octet-stream', + 'content-length': '7', + }, + raw, + tenantId: 'stub-tenant', + } as unknown as FastifyRequest, + { + objectName: 'test.txt', + fileSizeLimit: 150, + } + ) + + const objectAdminDeleteSendSpy = vi + .spyOn(ObjectAdminDelete, 'send') + .mockResolvedValue(undefined) + + const uploader = createUploader( + { + uploadObject: vi.fn(async (_bucket, _key, _version, body: Readable) => { + body.destroy(new Error('stream pipeline failed')) + throw StorageBackendError.fromError(new Error('socket hang up')) + }), + }, + { + tenantId: 'stub-tenant', + reqId: 'req-1', + tenant: () => ({ ref: 'stub-tenant', host: 'stub-tenant.local' }), + testPermission: vi.fn().mockResolvedValue(undefined), + } + ) + + try { + await uploader.upload({ + bucketId: 'bucket', + objectName: 'test.txt', + file, + uploadType: 'standard', + }) + throw new Error('Expected upload to throw') + } catch (error) { + expect(error).toBeInstanceOf(StorageBackendError) + expect((error as StorageBackendError).shouldCloseConnection()).toBe(true) + expect((error as StorageBackendError).message).toBe('socket hang up') + } finally { + objectAdminDeleteSendSpy.mockRestore() + } + }) + + test('keeps declared request size for permission checks but omits backend size for request-backed uploads', async () => { + const capturedWrites: Array<{ metadata?: { contentLength?: number } }> = [] + const backend = { + uploadObject: vi.fn().mockResolvedValue({ + httpStatusCode: 200, + cacheControl: 'no-cache', + eTag: '"etag"', + mimetype: 'text/plain', + contentLength: 7, + lastModified: new Date(), + size: 7, + contentRange: undefined, + }), + } + const uploader = createUploader(backend, { + tenantId: 'stub-tenant', + reqId: 'req-1', + tenant: () => ({ ref: 'stub-tenant', host: 'stub-tenant.local' }), + testPermission: vi.fn(async (fn) => + fn({ + createObject: vi.fn(async (payload: { metadata?: { contentLength?: number } }) => { + capturedWrites.push(payload) + }), + upsertObject: vi.fn(async (payload: { metadata?: { contentLength?: number } }) => { + capturedWrites.push(payload) + }), + }) + ), + }) + const completeUploadSpy = vi.spyOn(uploader, 'completeUpload').mockResolvedValue({ + metadata: { eTag: '"etag"' }, + obj: { id: 'obj-id' }, + } as CompleteUploadResult) + + await uploader.upload({ + bucketId: 'bucket', + objectName: 'test.txt', + uploadType: 'standard', + file: { + body: Readable.from(['payload']), + mimeType: 'text/plain', + cacheControl: 'no-cache', + declaredContentLength: 7, + isTruncated: () => false, + }, + }) + + expect(capturedWrites[0]?.metadata?.contentLength).toBe(7) + expect(backend.uploadObject).toHaveBeenCalledTimes(1) + expect(backend.uploadObject.mock.calls[0][7]).toBeUndefined() + + completeUploadSpy.mockRestore() + }) +}) diff --git a/src/test/utils/cache-metrics.ts b/src/test/utils/cache-metrics.ts new file mode 100644 index 000000000..d2fd8c64f --- /dev/null +++ b/src/test/utils/cache-metrics.ts @@ -0,0 +1,82 @@ +import type { Mock, MockInstance } from 'vitest' + +type CacheMetricAttributes = { + cache?: string + outcome?: string +} + +type CacheRequestMetricCall = [number, { cache: string; outcome: string }] + +type AssertLogicalLookupMetricsOptions = { + addSpy: MockInstance + backendCallSpy: Mock | MockInstance + cacheName: string + startLookups: () => [Promise, Promise, Promise] + resolveBackend: () => void + assertConcurrentResults?: (results: [T, T, T]) => void | Promise + assertCachedHit: () => Promise +} + +async function waitForImmediate(): Promise { + await new Promise((resolve) => setImmediate(resolve)) +} + +export function getCacheRequestCalls( + addSpy: MockInstance, + cacheName: string +): CacheRequestMetricCall[] { + return addSpy.mock.calls.filter((call) => { + const [, attrs] = call as [number, CacheMetricAttributes | undefined] + const metricAttrs = attrs as CacheMetricAttributes | undefined + + return Boolean( + metricAttrs && + typeof metricAttrs === 'object' && + metricAttrs.outcome && + metricAttrs.cache === cacheName + ) + }) as CacheRequestMetricCall[] +} + +export async function assertLogicalLookupMetrics({ + addSpy, + backendCallSpy, + cacheName, + startLookups, + resolveBackend, + assertConcurrentResults, + assertCachedHit, +}: AssertLogicalLookupMetricsOptions): Promise { + await waitForImmediate() + addSpy.mockClear() + + const lookups = startLookups() + + await waitForImmediate() + + // Each caller records its initial logical miss. + // Waiters re-check the cache inside + // the mutex with recordMetrics: false + // so only these three outer misses are emitted. + const misses = [ + [1, { cache: cacheName, outcome: 'miss' }], + [1, { cache: cacheName, outcome: 'miss' }], + [1, { cache: cacheName, outcome: 'miss' }], + ] + expect(getCacheRequestCalls(addSpy, cacheName)).toEqual(misses) + expect(backendCallSpy).toHaveBeenCalledTimes(1) + + resolveBackend() + + const results = (await Promise.all(lookups)) as [T, T, T] + await assertConcurrentResults?.(results) + + expect(getCacheRequestCalls(addSpy, cacheName)).toEqual(misses) + + await assertCachedHit() + + expect(getCacheRequestCalls(addSpy, cacheName)).toEqual([ + ...misses, + [1, { cache: cacheName, outcome: 'hit' }], + ]) +} diff --git a/src/test/utils/cache-mock.ts b/src/test/utils/cache-mock.ts new file mode 100644 index 000000000..38c3578ec --- /dev/null +++ b/src/test/utils/cache-mock.ts @@ -0,0 +1,25 @@ +export function mockCreateLruCache(overrides: Record): void { + vi.doMock('@internal/cache', async () => { + const actual = await vi.importActual('@internal/cache') + + return { + ...actual, + createLruCache: ((optionsOrName: unknown, maybeOptions?: Record) => { + if (typeof optionsOrName === 'string') { + return actual.createLruCache( + optionsOrName as never, + { + ...(maybeOptions || {}), + ...overrides, + } as never + ) + } + + return actual.createLruCache({ + ...(optionsOrName as Record), + ...overrides, + } as never) + }) as typeof actual.createLruCache, + } + }) +} diff --git a/src/test/utils/promise.ts b/src/test/utils/promise.ts new file mode 100644 index 000000000..6576203e6 --- /dev/null +++ b/src/test/utils/promise.ts @@ -0,0 +1,24 @@ +export async function waitForEventually( + getValue: () => Promise, + predicate: (value: T) => boolean, + description: string, + timeoutMs = 5000, + intervalMs = 25 +): Promise { + const deadline = Date.now() + timeoutMs + let lastValue: T | undefined + + while (Date.now() <= deadline) { + lastValue = await getValue() + + if (predicate(lastValue)) { + return lastValue + } + + await new Promise((resolve) => setTimeout(resolve, intervalMs)) + } + + throw new Error( + `Timed out after ${timeoutMs}ms waiting for ${description}. Last value: ${JSON.stringify(lastValue)}` + ) +} diff --git a/src/test/utils/seeds/seeder.ts b/src/test/utils/seeds/seeder.ts index 9bf1274f3..390be1a21 100644 --- a/src/test/utils/seeds/seeder.ts +++ b/src/test/utils/seeds/seeder.ts @@ -1,6 +1,6 @@ import { KnexPersistence } from '@internal/testing/seeder' -import { BucketsSeeder } from './bucket' import { Knex } from 'knex' +import { BucketsSeeder } from './bucket' export class TestUtils { private persistence: KnexPersistence diff --git a/src/test/utils/storage.ts b/src/test/utils/storage.ts index 700c2cfaf..2cb884fe6 100644 --- a/src/test/utils/storage.ts +++ b/src/test/utils/storage.ts @@ -1,18 +1,38 @@ +import { CreateBucketCommand, HeadBucketCommand, S3Client } from '@aws-sdk/client-s3' import { getPostgresConnection, getServiceKeyUser, TenantConnection } from '@internal/database' -import { Storage } from '@storage/storage' +import { isS3Error } from '@internal/errors' import { createStorageBackend, StorageBackendAdapter } from '@storage/backend' import { Database, StorageKnexDB } from '@storage/database' +import { StorageObjectLocator, TenantLocation } from '@storage/locator' import { ObjectScanner } from '@storage/scanner/scanner' -import { getConfig } from '../../config' +import { Storage } from '@storage/storage' import { Uploader } from '@storage/uploader' -import { StorageObjectLocator, TenantLocation } from '@storage/locator' -import { CreateBucketCommand, HeadBucketCommand, S3Client } from '@aws-sdk/client-s3' -import { isS3Error } from '@internal/errors' +import { Knex } from 'knex' +import { getConfig } from '../../config' + // import { CreateBucketCommand, HeadBucketCommand, S3Client } from '@aws-sdk/client-s3' // import { isS3Error } from '@internal/errors' const { tenantId, storageBackendType, storageS3Bucket } = getConfig() +/** + * Helper function to execute raw database operations in tests with storage.allow_delete_query set + * This is needed because raw queries bypass the normal connection scope setting + */ +export async function withDeleteEnabled(db: Knex, fn: (db: Knex) => Promise): Promise { + // Wrap in a transaction to ensure set_config applies to all operations + const tnx = await db.transaction() + try { + await tnx.raw(`SELECT set_config('storage.allow_delete_query', 'true', true)`) + const result = await fn(tnx) + await tnx.commit() + return result + } catch (e) { + await tnx.rollback() + throw e + } +} + export function useStorage() { let connection: TenantConnection let storage: Storage diff --git a/src/test/vectors.test.ts b/src/test/vectors.test.ts new file mode 100644 index 000000000..894a1dcfd --- /dev/null +++ b/src/test/vectors.test.ts @@ -0,0 +1,2348 @@ +import { + CreateIndexCommandOutput, + DeleteVectorsOutput, + GetVectorsCommandOutput, + ListVectorsOutput, + PutVectorsOutput, + QueryVectorsOutput, +} from '@aws-sdk/client-s3vectors' +import { signJWT } from '@internal/auth' +import { SingleShard } from '@internal/sharding' +import { KnexVectorMetadataDB, VectorStore, VectorStoreManager } from '@storage/protocols/vector' +import { FastifyInstance } from 'fastify' +import type { Mocked } from 'vitest' +import app from '../app' +import { getConfig, mergeConfig } from '../config' +import { useMockObject, useMockQueue } from './common' +import { useStorage } from './utils/storage' + +const { serviceKeyAsync, vectorS3Buckets, tenantId, jwtSecret } = getConfig() + +const vectorBucketS3 = vectorS3Buckets[0] + +let appInstance: FastifyInstance +let serviceToken: string + +type ListVectorBucketsResponse = Awaited> +type ListIndexesResponse = Awaited> + +function parseJsonBody(body: string): Body { + return JSON.parse(body) as Body +} + +// Use the common mock helpers +useMockObject() +useMockQueue() + +const { mockVectorStore } = vi.hoisted(() => ({ + mockVectorStore: { + deleteVectorIndex: vi.fn().mockResolvedValue({} as CreateIndexCommandOutput), + createVectorIndex: vi.fn().mockResolvedValue({} as CreateIndexCommandOutput), + putVectors: vi.fn().mockResolvedValue({} as PutVectorsOutput), + listVectors: vi.fn().mockResolvedValue({} as ListVectorsOutput), + queryVectors: vi.fn().mockResolvedValue({} as QueryVectorsOutput), + deleteVectors: vi.fn().mockResolvedValue({} as DeleteVectorsOutput), + getVectors: vi.fn().mockResolvedValue({} as GetVectorsCommandOutput), + } as Mocked, +})) + +vi.mock('@storage/protocols/vector/adapter/s3-vector', () => { + return { + S3Vector: vi.fn(function () { + return mockVectorStore + }), + createS3VectorClient: vi.fn().mockReturnValue({}), + ...mockVectorStore, + } +}) + +let vectorBucketName: string +let s3Vector: VectorStoreManager + +describe('Vectors API', () => { + const storageTest = useStorage() + + beforeAll(async () => { + appInstance = app() + + // Create service role token + serviceToken = await serviceKeyAsync + + // Create real S3Vector instance with mocked client and mock DB + const shard = new SingleShard({ + shardKey: 'test-bucket', + capacity: 1000, + }) + const mockVectorDB = new KnexVectorMetadataDB(storageTest.database.connection.pool.acquire()) + s3Vector = new VectorStoreManager(mockVectorStore, mockVectorDB, shard, { + tenantId: 'test-tenant', + maxBucketCount: Infinity, + maxIndexCount: Infinity, + }) + + // Decorate fastify instance with real S3Vector + appInstance.decorate('s3Vector', s3Vector) + }) + + afterAll(async () => { + await appInstance.close() + await storageTest.database.connection.dispose() + }) + + beforeEach(async () => { + vi.clearAllMocks() + vi.resetAllMocks() + + getConfig({ reload: true }) + mergeConfig({ vectorMaxBucketsCount: Infinity, vectorMaxIndexesCount: Infinity }) + + vectorBucketName = `test-bucket-${Date.now()}` + await s3Vector.createBucket(vectorBucketName) + }) + + describe('POST /vector/CreateIndex', () => { + let validCreateIndexRequest: { + dataType: 'float32' + dimension: number + distanceMetric: 'cosine' | 'euclidean' + indexName: string + vectorBucketName: string + metadataConfiguration?: { + nonFilterableMetadataKeys: string[] + } + } + beforeEach(async () => { + validCreateIndexRequest = { + dataType: 'float32', + dimension: 1536, + distanceMetric: 'cosine', + indexName: 'test-index', + vectorBucketName, + metadataConfiguration: { + nonFilterableMetadataKeys: ['key1', 'key2'], + }, + } + }) + + it('should create vector index successfully with valid request', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: validCreateIndexRequest, + }) + + expect(response.statusCode).toBe(200) + + // Verify the CreateIndexCommand was called with correct parameters including tenantId prefix + const createIndexCommand = mockVectorStore.createVectorIndex + expect(createIndexCommand).toHaveBeenCalledWith({ + ...validCreateIndexRequest, + vectorBucketName: vectorBucketS3, + indexName: `${tenantId}-test-index`, + }) + + const indexMetadata = await storageTest.database.connection.pool + .acquire() + .table('storage.vector_indexes') + .where({ + name: validCreateIndexRequest.indexName, + bucket_id: validCreateIndexRequest.vectorBucketName, + }) + .first() + + expect(indexMetadata).toBeDefined() + expect(indexMetadata?.data_type).toBe(validCreateIndexRequest.dataType) + expect(indexMetadata?.dimension).toBe(validCreateIndexRequest.dimension) + expect(indexMetadata?.distance_metric).toBe(validCreateIndexRequest.distanceMetric) + expect(indexMetadata?.metadata_configuration).toEqual( + validCreateIndexRequest.metadataConfiguration + ) + }) + + it('should require authentication with service role', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + payload: validCreateIndexRequest, + }) + + expect(response.statusCode).toBe(403) + // Vector service not called when validation fails + }) + + it('should reject request with invalid JWT role', async () => { + const invalidToken = 'invalid-token' + + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${invalidToken}`, + }, + payload: validCreateIndexRequest, + }) + + expect(response.statusCode).toBe(400) + // Vector service not called when validation fails + }) + + it('should validate required fields', async () => { + const incompleteRequest = { + dataType: 'float32', + dimension: 1536, + // missing required fields + } + + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: incompleteRequest, + }) + + expect(response.statusCode).toBe(400) + // Vector service not called when validation fails + }) + + it('should validate dataType enum', async () => { + const invalidRequest = { + ...validCreateIndexRequest, + dataType: 'invalid-type', + } + + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: invalidRequest, + }) + + expect(response.statusCode).toBe(400) + // Vector service not called when validation fails + }) + + it('should validate distanceMetric enum', async () => { + const invalidRequest = { + ...validCreateIndexRequest, + distanceMetric: 'invalid-metric', + } + + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: invalidRequest, + }) + + expect(response.statusCode).toBe(400) + // Vector service not called when validation fails + }) + + it('should validate dimension is a number', async () => { + const invalidRequest = { + ...validCreateIndexRequest, + dimension: 'not-a-number', + } + + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: invalidRequest, + }) + + expect(response.statusCode).toBe(400) + // Vector service not called when validation fails + }) + + it('should validate metadataConfiguration structure', async () => { + const invalidRequest = { + ...validCreateIndexRequest, + metadataConfiguration: { + // missing required nonFilterableMetadataKeys + invalidKey: 'value', + }, + } + + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: invalidRequest, + }) + + expect(response.statusCode).toBe(400) + // Vector service not called when validation fails + }) + + it('should handle vector service not configured', async () => { + mergeConfig({ vectorEnabled: false }) + + const appWithoutVector = app() + + try { + const response = await appWithoutVector.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: validCreateIndexRequest, + }) + + expect(response.statusCode).toBe(404) + const body = JSON.parse(response.body) + expect(body.error).toBe('Not Found') + } finally { + await appWithoutVector.close() + } + }) + + it('should return FeatureNotEnabled when the vector backend is not configured', async () => { + mergeConfig({ vectorEnabled: true, vectorS3Buckets: [] }) + + const appWithoutVector = app() + + try { + const response = await appWithoutVector.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: validCreateIndexRequest, + }) + + expect(response.statusCode).toBe(409) + expect(JSON.parse(response.body)).toMatchObject({ + statusCode: '409', + code: 'FeatureNotEnabled', + error: 'FeatureNotEnabled', + }) + } finally { + await appWithoutVector.close() + } + }) + + it('should handle S3Vector service errors', async () => { + const s3Error = new Error('S3VectorsClient error') + // Mock error - need to cast to bypass type restrictions + mockVectorStore.createVectorIndex.mockRejectedValue(s3Error) + + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: validCreateIndexRequest, + }) + + expect(response.statusCode).toBe(500) + expect(mockVectorStore.createVectorIndex).toHaveBeenCalledTimes(1) + }) + + it('should accept valid request without optional metadataConfiguration', async () => { + const requestWithoutMetadata = { + dataType: 'float32' as const, + dimension: 1536, + distanceMetric: 'euclidean' as const, + indexName: 'test-index-2', + vectorBucketName, + } + + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: requestWithoutMetadata, + }) + + expect(response.statusCode).toBe(200) + expect(mockVectorStore.createVectorIndex).toHaveBeenCalledTimes(1) + expect(mockVectorStore.createVectorIndex).toHaveBeenCalledWith({ + ...requestWithoutMetadata, + vectorBucketName: vectorBucketS3, + indexName: `${tenantId}-test-index-2`, + }) + }) + }) + + describe('POST /vector/CreateVectorBucket', () => { + beforeEach(async () => {}) + + it('should create vector bucket successfully with valid request', async () => { + const newBucketName = `test-bucket-${Date.now()}-new` + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateVectorBucket', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName: newBucketName, + }, + }) + + expect(response.statusCode).toBe(200) + + // Verify bucket was created in database + const bucketRecord = await storageTest.database.connection.pool + .acquire() + .table('storage.buckets_vectors') + .where({ id: newBucketName }) + .first() + + expect(bucketRecord).toBeDefined() + expect(bucketRecord?.id).toBe(newBucketName) + expect(bucketRecord?.created_at).toBeDefined() + }) + + it('should require authentication with service role', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateVectorBucket', + payload: { + vectorBucketName: 'test-bucket', + }, + }) + + expect(response.statusCode).toBe(403) + }) + + it('should reject request with invalid JWT role', async () => { + const token = await signJWT({ role: 'auth', sub: '1234' }, jwtSecret, '1h') + + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateVectorBucket', + headers: { + authorization: `Bearer ${token}`, + }, + payload: { + vectorBucketName: 'test-bucket', + }, + }) + + expect(response.statusCode).toBe(403) + }) + + it('should validate required fields', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateVectorBucket', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: {}, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should handle duplicate bucket creation gracefully', async () => { + // First creation + const newVectorBucketName = `test-bucket-${Date.now()}-dup` + const response1 = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateVectorBucket', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName: newVectorBucketName, + }, + }) + + expect(response1.statusCode).toBe(200) + + // Second creation should return conflict + const response2 = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateVectorBucket', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName: newVectorBucketName, + }, + }) + + expect(response2.statusCode).toBe(409) + }) + }) + + describe('POST /vector/DeleteVectorBucket', () => { + beforeEach(async () => {}) + it('should delete empty vector bucket successfully', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/DeleteVectorBucket', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(200) + + // Verify bucket was deleted from database + const bucketRecord = await storageTest.database.connection.pool + .acquire() + .table('storage.buckets_vectors') + .where({ id: vectorBucketName }) + .first() + + expect(bucketRecord).toBeUndefined() + }) + + it('should fail when trying to delete bucket with indexes', async () => { + // First create an index in the bucket + await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + dataType: 'float32', + dimension: 1536, + distanceMetric: 'cosine', + indexName: 'test-index', + vectorBucketName, + }, + }) + + // Try to delete the bucket + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/DeleteVectorBucket', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(400) + const body = JSON.parse(response.body) + expect(body.error).toBe('VectorBucketNotEmpty') + }) + + it('should require authentication with service role', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/DeleteVectorBucket', + payload: { + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(403) + }) + + it('should validate required fields', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/DeleteVectorBucket', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: {}, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should handle non-existent bucket', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/DeleteVectorBucket', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName: 'non-existent-bucket', + }, + }) + + expect(response.statusCode).toBe(200) + }) + }) + + describe('POST /vector/ListVectorBuckets', () => { + beforeEach(async () => { + // Create multiple buckets for listing + await s3Vector.createBucket(`test-bucket-a-${Date.now()}`) + await s3Vector.createBucket(`test-bucket-b-${Date.now()}`) + await s3Vector.createBucket(`test-bucket-c-${Date.now()}`) + }) + + it('should list all vector buckets', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectorBuckets', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: {}, + }) + + expect(response.statusCode).toBe(200) + const body = parseJsonBody(response.body) + expect(body.vectorBuckets).toBeDefined() + expect(Array.isArray(body.vectorBuckets)).toBe(true) + expect(body.vectorBuckets.length).toBeGreaterThan(0) + + // Verify structure of bucket objects + body.vectorBuckets.forEach((bucket) => { + expect(bucket.vectorBucketName).toBeDefined() + expect(bucket.creationTime).toBeDefined() + expect(typeof bucket.creationTime).toBe('number') + }) + }) + + it('should support maxResults parameter', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectorBuckets', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + maxResults: 2, + }, + }) + + expect(response.statusCode).toBe(200) + const body = JSON.parse(response.body) + expect(body.vectorBuckets.length).toBeLessThanOrEqual(2) + if (body.vectorBuckets.length === 2) { + expect(body.nextToken).toBeDefined() + } + }) + + it('should support pagination with nextToken', async () => { + const response1 = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectorBuckets', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + maxResults: 1, + }, + }) + + const body1 = JSON.parse(response1.body) + + if (body1.nextToken) { + const response2 = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectorBuckets', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + maxResults: 1, + nextToken: body1.nextToken, + }, + }) + + expect(response2.statusCode).toBe(200) + const body2 = JSON.parse(response2.body) + expect(body2.vectorBuckets).toBeDefined() + + // Ensure different buckets are returned + if (body2.vectorBuckets.length > 0) { + expect(body1.vectorBuckets[0].vectorBucketName).not.toBe( + body2.vectorBuckets[0].vectorBucketName + ) + } + } + }) + + it('should support prefix filtering', async () => { + const prefix = 'test-bucket-a' + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectorBuckets', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + prefix, + }, + }) + + expect(response.statusCode).toBe(200) + const body = parseJsonBody(response.body) + body.vectorBuckets.forEach((bucket) => { + expect(bucket.vectorBucketName).toMatch(new RegExp(`^${prefix}`)) + }) + }) + + it('supports prefix filtering with nextToken for buckets', async () => { + const runId = Date.now().toString(36) + const prefix = `page-bucket-${runId}` + const firstBucket = `${prefix}-a` + const secondBucket = `${prefix}-b` + + await s3Vector.createBucket(firstBucket) + await s3Vector.createBucket(secondBucket) + await s3Vector.createBucket(`other-${runId}-bucket`) + + const page1Response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectorBuckets', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + prefix, + maxResults: 1, + }, + }) + + expect(page1Response.statusCode).toBe(200) + const page1 = JSON.parse(page1Response.body) + expect(page1.vectorBuckets).toHaveLength(1) + expect(page1.vectorBuckets[0].vectorBucketName.startsWith(prefix)).toBe(true) + expect(page1.nextToken).toBeDefined() + + const page2Response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectorBuckets', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + prefix, + maxResults: 1, + nextToken: page1.nextToken, + }, + }) + + expect(page2Response.statusCode).toBe(200) + const page2 = JSON.parse(page2Response.body) + expect(page2.vectorBuckets).toHaveLength(1) + expect(page2.vectorBuckets[0].vectorBucketName.startsWith(prefix)).toBe(true) + expect(page2.vectorBuckets[0].vectorBucketName).not.toBe( + page1.vectorBuckets[0].vectorBucketName + ) + }) + + it('returns nextToken only on truncated bucket pages', async () => { + const runId = Date.now().toString(36) + const prefix = `terminal-bucket-${runId}` + + await s3Vector.createBucket(`${prefix}-a`) + await s3Vector.createBucket(`${prefix}-b`) + + const page1Response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectorBuckets', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + prefix, + maxResults: 1, + }, + }) + + expect(page1Response.statusCode).toBe(200) + const page1 = JSON.parse(page1Response.body) + expect(page1.nextToken).toBeDefined() + + const page2Response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectorBuckets', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + prefix, + maxResults: 1, + nextToken: page1.nextToken, + }, + }) + + expect(page2Response.statusCode).toBe(200) + const page2 = JSON.parse(page2Response.body) + expect(page2.vectorBuckets).toHaveLength(1) + expect(page2.nextToken).toBeUndefined() + }) + + it('treats % as a literal character in bucket prefix filtering', async () => { + const runId = Date.now().toString(36) + const prefix = `%literal-${runId}` + const matchingBucket = `${prefix}-bucket` + const nonMatchingBucket = `x${prefix}-bucket` + + await s3Vector.createBucket(matchingBucket) + await s3Vector.createBucket(nonMatchingBucket) + + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectorBuckets', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + prefix, + }, + }) + + expect(response.statusCode).toBe(200) + const body = parseJsonBody(response.body) + const names = body.vectorBuckets.map((bucket) => bucket.vectorBucketName) + expect(names).toContain(matchingBucket) + expect(names).not.toContain(nonMatchingBucket) + }) + + it('treats _ as a literal character in bucket prefix filtering', async () => { + const runId = Date.now().toString(36) + const prefix = `_literal-${runId}` + const matchingBucket = `${prefix}-bucket` + const nonMatchingBucket = `aliteral-${runId}-bucket` + + await s3Vector.createBucket(matchingBucket) + await s3Vector.createBucket(nonMatchingBucket) + + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectorBuckets', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + prefix, + }, + }) + + expect(response.statusCode).toBe(200) + const body = parseJsonBody(response.body) + const names = body.vectorBuckets.map((bucket) => bucket.vectorBucketName) + expect(names).toContain(matchingBucket) + expect(names).not.toContain(nonMatchingBucket) + }) + + it('should require authentication with service role', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectorBuckets', + payload: {}, + }) + + expect(response.statusCode).toBe(403) + }) + }) + + describe('POST /vector/GetVectorBucket', () => { + it('should get vector bucket details successfully', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/GetVectorBucket', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(200) + const body = JSON.parse(response.body) + expect(body.vectorBucket).toBeDefined() + expect(body.vectorBucket.vectorBucketName).toBe(vectorBucketName) + expect(body.vectorBucket.creationTime).toBeDefined() + expect(typeof body.vectorBucket.creationTime).toBe('number') + }) + + it('should require authentication with service role', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/GetVectorBucket', + payload: { + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(403) + }) + + it('should validate required fields', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/GetVectorBucket', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: {}, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should handle non-existent bucket', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/GetVectorBucket', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName: 'non-existent-bucket', + }, + }) + + expect(response.statusCode).toBe(404) + }) + }) + + describe('POST /vector/DeleteIndex', () => { + let indexName: string + + beforeEach(async () => { + vectorBucketName = `test-delete-index-${Date.now()}` + await s3Vector.createBucket(vectorBucketName) + + indexName = `test-index-${Date.now()}` + // Create an index first + + await s3Vector.createVectorIndex({ + dataType: 'float32', + dimension: 1536, + distanceMetric: 'cosine', + indexName, + vectorBucketName, + }) + }) + + it('should delete vector index successfully', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/DeleteIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + indexName, + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(200) + + // Verify the index was deleted from database + const indexRecord = await storageTest.database.connection.pool + .acquire() + .table('storage.vector_indexes') + .where({ + name: indexName, + bucket_id: vectorBucketName, + }) + .first() + + expect(indexRecord).toBeUndefined() + + // Verify deleteVectorIndex was called with correct parameters + expect(mockVectorStore.deleteVectorIndex).toHaveBeenCalledWith({ + vectorBucketName: vectorBucketS3, + indexName: `${tenantId}-${indexName}`, + }) + }) + + it('should require authentication with service role', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/DeleteIndex', + payload: { + indexName, + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(403) + }) + + it('should validate required fields', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/DeleteIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + indexName, + }, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should validate indexName pattern', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/DeleteIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + indexName: 'INVALID_NAME', + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should handle non-existent index', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/DeleteIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + indexName: 'non-existent-index', + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(404) + }) + }) + + describe('POST /vector/ListIndexes', () => { + beforeEach(async () => { + // Create multiple indexes for listing + await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + dataType: 'float32', + dimension: 1536, + distanceMetric: 'cosine', + indexName: `index-a-${Date.now()}`, + vectorBucketName, + }, + }) + + await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + dataType: 'float32', + dimension: 768, + distanceMetric: 'euclidean', + indexName: `index-b-${Date.now()}`, + vectorBucketName, + }, + }) + }) + + it('should list all indexes in a bucket', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListIndexes', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(200) + const body = parseJsonBody(response.body) + expect(body.indexes).toBeDefined() + expect(Array.isArray(body.indexes)).toBe(true) + expect(body.indexes.length).toBeGreaterThanOrEqual(2) + + // Verify structure of index objects + body.indexes.forEach((index) => { + expect(index.indexName).toBeDefined() + expect(index.vectorBucketName).toBe(vectorBucketName) + expect(index.creationTime).toBeDefined() + expect(typeof index.creationTime).toBe('number') + }) + }) + + it('should support maxResults parameter', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListIndexes', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + maxResults: 1, + }, + }) + + expect(response.statusCode).toBe(200) + const body = JSON.parse(response.body) + expect(body.indexes.length).toBeLessThanOrEqual(1) + }) + + it('supports pagination with nextToken for indexes', async () => { + const page1Response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListIndexes', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + maxResults: 1, + }, + }) + + expect(page1Response.statusCode).toBe(200) + const page1 = JSON.parse(page1Response.body) + expect(page1.indexes).toHaveLength(1) + expect(page1.nextToken).toBeDefined() + + const page2Response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListIndexes', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + maxResults: 1, + nextToken: page1.nextToken, + }, + }) + + expect(page2Response.statusCode).toBe(200) + const page2 = JSON.parse(page2Response.body) + expect(page2.indexes).toHaveLength(1) + expect(page2.indexes[0].indexName).not.toBe(page1.indexes[0].indexName) + }) + + it('should support prefix filtering', async () => { + const prefix = 'index-a' + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListIndexes', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + prefix, + }, + }) + + expect(response.statusCode).toBe(200) + const body = parseJsonBody(response.body) + body.indexes.forEach((index) => { + expect(index.indexName).toMatch(new RegExp(`^${prefix}`)) + }) + }) + + it('supports prefix filtering with nextToken for indexes', async () => { + const runId = Date.now().toString(36) + const prefix = `page-index-${runId}` + + const createFirstResponse = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + dataType: 'float32', + dimension: 128, + distanceMetric: 'cosine', + indexName: `${prefix}-a`, + vectorBucketName, + }, + }) + expect(createFirstResponse.statusCode).toBe(200) + + const createSecondResponse = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + dataType: 'float32', + dimension: 128, + distanceMetric: 'cosine', + indexName: `${prefix}-b`, + vectorBucketName, + }, + }) + expect(createSecondResponse.statusCode).toBe(200) + + const page1Response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListIndexes', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + prefix, + maxResults: 1, + }, + }) + + expect(page1Response.statusCode).toBe(200) + const page1 = JSON.parse(page1Response.body) + expect(page1.indexes).toHaveLength(1) + expect(page1.indexes[0].indexName.startsWith(prefix)).toBe(true) + expect(page1.nextToken).toBeDefined() + + const page2Response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListIndexes', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + prefix, + maxResults: 1, + nextToken: page1.nextToken, + }, + }) + + expect(page2Response.statusCode).toBe(200) + const page2 = JSON.parse(page2Response.body) + expect(page2.indexes).toHaveLength(1) + expect(page2.indexes[0].indexName.startsWith(prefix)).toBe(true) + expect(page2.indexes[0].indexName).not.toBe(page1.indexes[0].indexName) + }) + + it('returns nextToken only on truncated index pages', async () => { + const runId = Date.now().toString(36) + const prefix = `terminal-index-${runId}` + + const createFirstResponse = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + dataType: 'float32', + dimension: 128, + distanceMetric: 'cosine', + indexName: `${prefix}-a`, + vectorBucketName, + }, + }) + expect(createFirstResponse.statusCode).toBe(200) + + const createSecondResponse = await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + dataType: 'float32', + dimension: 128, + distanceMetric: 'cosine', + indexName: `${prefix}-b`, + vectorBucketName, + }, + }) + expect(createSecondResponse.statusCode).toBe(200) + + const page1Response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListIndexes', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + prefix, + maxResults: 1, + }, + }) + + expect(page1Response.statusCode).toBe(200) + const page1 = JSON.parse(page1Response.body) + expect(page1.nextToken).toBeDefined() + + const page2Response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListIndexes', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + prefix, + maxResults: 1, + nextToken: page1.nextToken, + }, + }) + + expect(page2Response.statusCode).toBe(200) + const page2 = JSON.parse(page2Response.body) + expect(page2.indexes).toHaveLength(1) + expect(page2.nextToken).toBeUndefined() + }) + + it('treats % as a literal character in index prefix filtering', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListIndexes', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + prefix: '%', + }, + }) + + expect(response.statusCode).toBe(200) + const body = JSON.parse(response.body) + expect(body.indexes).toHaveLength(0) + }) + + it('treats _ as a literal character in index prefix filtering', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListIndexes', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + prefix: '_', + }, + }) + + expect(response.statusCode).toBe(200) + const body = JSON.parse(response.body) + expect(body.indexes).toHaveLength(0) + }) + + it('should require authentication with service role', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListIndexes', + payload: { + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(403) + }) + + it('should validate required fields', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListIndexes', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: {}, + }) + + expect(response.statusCode).toBe(400) + }) + }) + + describe('POST /vector/GetIndex', () => { + let indexName: string + + beforeEach(async () => { + indexName = `test-index-${Date.now()}` + await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + dataType: 'float32', + dimension: 1536, + distanceMetric: 'cosine', + indexName, + vectorBucketName, + metadataConfiguration: { + nonFilterableMetadataKeys: ['key1'], + }, + }, + }) + }) + + it('should get index details successfully', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/GetIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + indexName, + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(200) + const body = JSON.parse(response.body) + expect(body.index).toBeDefined() + expect(body.index.indexName).toBe(indexName) + expect(body.index.vectorBucketName).toBe(vectorBucketName) + expect(body.index.dataType).toBe('float32') + expect(body.index.dimension).toBe(1536) + expect(body.index.distanceMetric).toBe('cosine') + expect(body.index.metadataConfiguration).toEqual({ + nonFilterableMetadataKeys: ['key1'], + }) + expect(body.index.creationTime).toBeDefined() + expect(typeof body.index.creationTime).toBe('number') + }) + + it('should require authentication with service role', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/GetIndex', + payload: { + indexName, + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(403) + }) + + it('should validate required fields', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/GetIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + indexName, + }, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should validate indexName pattern', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/GetIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + indexName: 'INVALID_NAME', + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should handle non-existent index', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/GetIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + indexName: 'non-existent-index', + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(404) + }) + }) + + describe('POST /vector/PutVectors', () => { + let indexName: string + + beforeEach(async () => { + indexName = `test-index-${Date.now()}` + await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + dataType: 'float32', + dimension: 3, + distanceMetric: 'cosine', + indexName, + vectorBucketName, + }, + }) + + mockVectorStore.putVectors.mockResolvedValue({ + vectorKeys: [{ key: 'vec1' }, { key: 'vec2' }], + } as PutVectorsOutput) + }) + + it('should put vector successfully', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/PutVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + vectors: [ + { + key: 'vec1', + data: { + float32: [1.0, 2.0, 3.0], + }, + metadata: { + category: 'test', + }, + }, + { + data: { + float32: [4.0, 5.0, 6.0], + }, + }, + ], + }, + }) + + expect(response.statusCode).toBe(200) + + // Verify putVectors was called with correct parameters + expect(mockVectorStore.putVectors).toHaveBeenCalledWith({ + indexName: `${tenantId}-${indexName}`, + vectors: [ + { + key: 'vec1', + data: { + float32: [1.0, 2.0, 3.0], + }, + metadata: { + category: 'test', + }, + }, + { + data: { + float32: [4.0, 5.0, 6.0], + }, + key: undefined, + }, + ], + vectorBucketName: vectorBucketS3, + }) + }) + + it('should require authentication with service role', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/PutVectors', + payload: { + vectorBucketName, + indexName, + vector: [ + { + data: { + float32: [1.0, 2.0, 3.0], + }, + }, + ], + }, + }) + + expect(response.statusCode).toBe(403) + }) + + it('should validate required fields', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/PutVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + }, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should validate vector data structure', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/PutVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + vector: [ + { + data: { + // missing float32 + }, + }, + ], + }, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should validate maxItems limit', async () => { + const tooManyVectors = Array.from({ length: 501 }, (_, i) => ({ + data: { + float32: [1.0, 2.0, 3.0], + }, + })) + + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/PutVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + vector: tooManyVectors, + }, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should handle non-existent index', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/PutVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName: 'non-existent-index', + vectors: [ + { + data: { + float32: [1.0, 2.0, 3.0], + }, + }, + ], + }, + }) + + expect(response.statusCode).toBe(404) + }) + }) + + describe('POST /vector/QueryVectors', () => { + let indexName: string + + beforeEach(async () => { + indexName = `test-index-${Date.now()}` + await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + dataType: 'float32', + dimension: 3, + distanceMetric: 'cosine', + indexName, + vectorBucketName, + }, + }) + + mockVectorStore.queryVectors.mockResolvedValue({ + vectors: [ + { + key: 'vec1', + distance: 0.95, + }, + ], + } as QueryVectorsOutput) + }) + + it('should query vector successfully', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/QueryVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + queryVector: { + float32: [1.0, 2.0, 3.0], + }, + topK: 10, + returnDistance: true, + returnMetadata: true, + }, + }) + + expect(response.statusCode).toBe(200) + const body = JSON.parse(response.body) + expect(body.vectors).toBeDefined() + + // Verify queryVectors was called with correct parameters + expect(mockVectorStore.queryVectors).toHaveBeenCalledWith({ + vectorBucketName: vectorBucketS3, + indexName: `${tenantId}-${indexName}`, + indexArn: undefined, + queryVector: { + float32: [1.0, 2.0, 3.0], + }, + topK: 10, + returnDistance: true, + returnMetadata: true, + filter: undefined, + }) + }) + + it('should support metadata filtering', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/QueryVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + queryVector: { + float32: [1.0, 2.0, 3.0], + }, + topK: 5, + filter: { + category: 'test', + }, + }, + }) + + expect(response.statusCode).toBe(200) + expect(mockVectorStore.queryVectors).toHaveBeenCalledWith( + expect.objectContaining({ + filter: { + category: 'test', + }, + }) + ) + }) + + it('should support complex logical filters', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/QueryVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + queryVector: { + float32: [1.0, 2.0, 3.0], + }, + topK: 5, + filter: { + $and: [{ category: 'test' }, { score: { $gt: 0.5 } }], + }, + }, + }) + + expect(response.statusCode).toBe(200) + expect(mockVectorStore.queryVectors).toHaveBeenCalledWith( + expect.objectContaining({ + filter: { + $and: [{ category: 'test' }, { score: { $gt: 0.5 } }], + }, + }) + ) + }) + + it('should require authentication with service role', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/QueryVectors', + payload: { + vectorBucketName, + indexName, + queryVector: { + float32: [1.0, 2.0, 3.0], + }, + topK: 10, + }, + }) + + expect(response.statusCode).toBe(403) + }) + + it('should validate required fields', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/QueryVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + queryVector: { + float32: [1.0, 2.0, 3.0], + }, + }, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should validate queryVector structure', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/QueryVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + queryVector: { + // missing float32 + }, + topK: 10, + }, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should handle non-existent index', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/QueryVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName: 'non-existent-index', + queryVector: { + float32: [1.0, 2.0, 3.0], + }, + topK: 10, + }, + }) + + expect(response.statusCode).toBe(404) + }) + }) + + describe('POST /vector/DeleteVectors', () => { + let indexName: string + + beforeEach(async () => { + indexName = `test-index-${Date.now()}` + await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + dataType: 'float32', + dimension: 3, + distanceMetric: 'cosine', + indexName, + vectorBucketName, + }, + }) + + mockVectorStore.deleteVectors.mockResolvedValue({} as DeleteVectorsOutput) + }) + + it('should delete vector successfully', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/DeleteVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + keys: ['vec1', 'vec2', 'vec3'], + }, + }) + + expect(response.statusCode).toBe(200) + + // Verify deleteVectors was called with correct parameters + expect(mockVectorStore.deleteVectors).toHaveBeenCalledWith({ + vectorBucketName: vectorBucketS3, + indexName: `${tenantId}-${indexName}`, + keys: ['vec1', 'vec2', 'vec3'], + }) + }) + + it('should require authentication with service role', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/DeleteVectors', + payload: { + vectorBucketName, + indexName, + keys: ['vec1'], + }, + }) + + expect(response.statusCode).toBe(403) + }) + + it('should validate required fields', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/DeleteVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + }, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should handle non-existent index', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/DeleteVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName: 'non-existent-index', + keys: ['vec1'], + }, + }) + + expect(response.statusCode).toBe(404) + }) + }) + + describe('POST /vector/ListVectors', () => { + let indexName: string + + beforeEach(async () => { + indexName = `test-index-${Date.now()}` + await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + dataType: 'float32', + dimension: 3, + distanceMetric: 'cosine', + indexName, + vectorBucketName, + }, + }) + + mockVectorStore.listVectors.mockResolvedValue({ + vectors: [{ key: 'vec1' }, { key: 'vec2' }, { key: 'vec3' }], + nextToken: undefined, + } as ListVectorsOutput) + }) + + it('should list vector successfully', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + }, + }) + + expect(response.statusCode).toBe(200) + const body = JSON.parse(response.body) + expect(body.vectors).toBeDefined() + expect(Array.isArray(body.vectors)).toBe(true) + + // Verify listVectors was called with correct parameters + expect(mockVectorStore.listVectors).toHaveBeenCalledWith( + expect.objectContaining({ + vectorBucketName: vectorBucketS3, + indexName: `${tenantId}-${indexName}`, + }) + ) + }) + + it('should support maxResults parameter', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + maxResults: 10, + }, + }) + + expect(response.statusCode).toBe(200) + expect(mockVectorStore.listVectors).toHaveBeenCalledWith( + expect.objectContaining({ + maxResults: 10, + }) + ) + }) + + it('should support returnData and returnMetadata flags', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + returnData: true, + returnMetadata: true, + }, + }) + + expect(response.statusCode).toBe(200) + expect(mockVectorStore.listVectors).toHaveBeenCalledWith( + expect.objectContaining({ + returnData: true, + returnMetadata: true, + }) + ) + }) + + it('should support pagination with nextToken', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + nextToken: 'some-token', + }, + }) + + expect(response.statusCode).toBe(200) + expect(mockVectorStore.listVectors).toHaveBeenCalledWith( + expect.objectContaining({ + nextToken: 'some-token', + }) + ) + }) + + it('should support segmentation parameters', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + segmentCount: 4, + segmentIndex: 2, + }, + }) + + expect(response.statusCode).toBe(200) + expect(mockVectorStore.listVectors).toHaveBeenCalledWith( + expect.objectContaining({ + segmentCount: 4, + segmentIndex: 2, + }) + ) + }) + + it('should require authentication with service role', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectors', + payload: { + vectorBucketName, + indexName, + }, + }) + + expect(response.statusCode).toBe(403) + }) + + it('should validate required fields', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + }, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should validate maxResults range', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + maxResults: 501, + }, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should validate segmentIndex range', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + segmentCount: 4, + segmentIndex: 16, + }, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should handle non-existent index', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/ListVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName: 'non-existent-index', + }, + }) + + expect(response.statusCode).toBe(404) + }) + }) + + describe('POST /vector/GetVectors', () => { + let indexName: string + + beforeEach(async () => { + indexName = `test-index-${Date.now()}` + await appInstance.inject({ + method: 'POST', + url: '/vector/CreateIndex', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + dataType: 'float32', + dimension: 3, + distanceMetric: 'cosine', + indexName, + vectorBucketName, + }, + }) + + mockVectorStore.getVectors.mockResolvedValue({ + vectors: [ + { + key: 'vec1', + data: { float32: [1.0, 2.0, 3.0] }, + metadata: { category: 'test' }, + }, + { + key: 'vec2', + data: { float32: [4.0, 5.0, 6.0] }, + metadata: { category: 'test2' }, + }, + ], + $metadata: { + httpStatusCode: 200, + }, + } as GetVectorsCommandOutput) + }) + + it('should get vector successfully', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/GetVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + keys: ['vec1', 'vec2'], + returnData: true, + returnMetadata: true, + }, + }) + + expect(response.statusCode).toBe(200) + const body = JSON.parse(response.body) + expect(body.vectors).toBeDefined() + expect(Array.isArray(body.vectors)).toBe(true) + + // Verify getVectors was called with correct parameters + expect(mockVectorStore.getVectors).toHaveBeenCalledWith({ + vectorBucketName: vectorBucketS3, + indexName: `${tenantId}-${indexName}`, + keys: ['vec1', 'vec2'], + returnData: true, + returnMetadata: true, + }) + }) + + it('should work with default returnData and returnMetadata', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/GetVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + keys: ['vec1'], + }, + }) + + expect(response.statusCode).toBe(200) + expect(mockVectorStore.getVectors).toHaveBeenCalledWith( + expect.objectContaining({ + returnData: false, + returnMetadata: false, + }) + ) + }) + + it('should require authentication with service role', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/GetVectors', + payload: { + vectorBucketName, + indexName, + keys: ['vec1'], + }, + }) + + expect(response.statusCode).toBe(403) + }) + + it('should validate required fields', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/GetVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName, + }, + }) + + expect(response.statusCode).toBe(400) + }) + + it('should handle non-existent index', async () => { + const response = await appInstance.inject({ + method: 'POST', + url: '/vector/GetVectors', + headers: { + authorization: `Bearer ${serviceToken}`, + }, + payload: { + vectorBucketName, + indexName: 'non-existent-index', + keys: ['vec1'], + }, + }) + + expect(response.statusCode).toBe(404) + }) + }) +}) diff --git a/src/test/vitest-setup.ts b/src/test/vitest-setup.ts new file mode 100644 index 000000000..f2465dfbc --- /dev/null +++ b/src/test/vitest-setup.ts @@ -0,0 +1,26 @@ +import { getConfig, setEnvPaths } from '../config' + +setEnvPaths(['.env.test', '.env']) + +interface OTelGlobalState { + __otelMetricsShutdown?: () => Promise + __otelTracingShutdown?: () => Promise +} + +beforeEach(() => { + getConfig({ reload: true }) +}) + +afterAll(async () => { + const otelGlobalState = globalThis as typeof globalThis & OTelGlobalState + const shutdownOtelTracing = otelGlobalState.__otelTracingShutdown + const shutdownOtelMetrics = otelGlobalState.__otelMetricsShutdown + + if (shutdownOtelTracing) { + await shutdownOtelTracing() + } + + if (shutdownOtelMetrics) { + await shutdownOtelMetrics() + } +}) diff --git a/src/test/webhooks.test.ts b/src/test/webhooks.test.ts index 38de7faf9..31def757f 100644 --- a/src/test/webhooks.test.ts +++ b/src/test/webhooks.test.ts @@ -1,22 +1,26 @@ import { TenantConnection } from '@internal/database' import { getConfig, mergeConfig } from '../config' +vi.hoisted(() => { + process.env.PG_QUEUE_ENABLE = 'true' +}) + const { serviceKeyAsync, tenantId } = getConfig() mergeConfig({ pgQueueEnable: true, }) -import { mockQueue, useMockObject } from './common' -import FormData from 'form-data' - -import fs from 'fs' -import app from '../app' -import { getPostgresConnection } from '@internal/database' +import { getPostgresConnection, getServiceKeyUser } from '@internal/database' import { Obj } from '@storage/schemas' import { randomUUID } from 'crypto' -import { getServiceKeyUser } from '@internal/database' import { FastifyInstance } from 'fastify' +import FormData from 'form-data' +import fs from 'fs' +import type { MockInstance } from 'vitest' +import app from '../app' +import { ObjectAdminDeleteAllBefore } from '../storage/events/objects/object-admin-delete-all-before' +import { mockQueue, useMockObject } from './common' describe('Webhooks', () => { useMockObject() @@ -33,7 +37,7 @@ describe('Webhooks', () => { }) let appInstance: FastifyInstance - let sendSpy: jest.SpyInstance + let sendSpy: MockInstance beforeEach(() => { const mocks = mockQueue() sendSpy = mocks.sendSpy @@ -42,7 +46,7 @@ describe('Webhooks', () => { afterEach(async () => { await appInstance.close() - jest.clearAllMocks() + vi.clearAllMocks() }) it('will emit a webhook upon object creation', async () => { @@ -63,7 +67,7 @@ describe('Webhooks', () => { payload: form, }) expect(response.statusCode).toBe(200) - expect(sendSpy).toBeCalledTimes(1) + expect(sendSpy).toHaveBeenCalledTimes(1) expect(sendSpy).toHaveBeenCalledWith( expect.objectContaining({ name: 'webhooks', @@ -114,7 +118,7 @@ describe('Webhooks', () => { }, }) expect(response.statusCode).toBe(200) - expect(sendSpy).toBeCalledTimes(1) + expect(sendSpy).toHaveBeenCalledTimes(1) expect(sendSpy).toHaveBeenNthCalledWith( 1, @@ -167,7 +171,7 @@ describe('Webhooks', () => { }) expect(response.statusCode).toBe(200) - expect(sendSpy).toBeCalledTimes(3) + expect(sendSpy).toHaveBeenCalledTimes(3) expect(sendSpy).toHaveBeenNthCalledWith( 2, @@ -251,6 +255,47 @@ describe('Webhooks', () => { ) }) + it('will emit destination bucket in ObjectCreated:Move payload for cross-bucket moves', async () => { + const obj = await createObject(pg, 'bucket6') + const destinationKey = `${obj.name}-moved-${randomUUID()}` + + const authorization = `Bearer ${await serviceKeyAsync}` + const response = await appInstance.inject({ + method: 'POST', + url: `/object/move`, + headers: { + authorization, + }, + payload: { + bucketId: 'bucket6', + sourceKey: obj.name, + destinationBucket: 'bucket2', + destinationKey, + }, + }) + + expect(response.statusCode).toBe(200) + expect(sendSpy).toHaveBeenCalledTimes(3) + expect(sendSpy).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ + data: expect.objectContaining({ + event: expect.objectContaining({ + type: 'ObjectCreated:Move', + payload: expect.objectContaining({ + bucketId: 'bucket2', + name: destinationKey, + oldObject: expect.objectContaining({ + bucketId: 'bucket6', + name: obj.name, + }), + }), + }), + }), + }) + ) + }) + it('will emit a webhook upon object copied', async () => { const obj = await createObject(pg, 'bucket6') @@ -269,7 +314,7 @@ describe('Webhooks', () => { }) expect(response.statusCode).toBe(200) - expect(sendSpy).toBeCalledTimes(1) + expect(sendSpy).toHaveBeenCalledTimes(1) expect(sendSpy).toHaveBeenCalledWith( expect.objectContaining({ @@ -310,10 +355,131 @@ describe('Webhooks', () => { }) ) }) + + it('will emit destination bucket in ObjectCreated:Copy payload for cross-bucket copies', async () => { + const obj = await createObject(pg, 'bucket6') + const destinationKey = `${obj.name}-copied-${randomUUID()}` + + const authorization = `Bearer ${await serviceKeyAsync}` + const response = await appInstance.inject({ + method: 'POST', + url: `/object/copy`, + headers: { + authorization, + }, + payload: { + bucketId: 'bucket6', + sourceKey: obj.name, + destinationBucket: 'bucket2', + destinationKey, + }, + }) + + expect(response.statusCode).toBe(200) + expect(sendSpy).toHaveBeenCalledTimes(1) + expect(sendSpy).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + event: expect.objectContaining({ + type: 'ObjectCreated:Copy', + payload: expect.objectContaining({ + bucketId: 'bucket2', + name: destinationKey, + }), + }), + }), + }) + ) + }) + + it('will emit webhooks for each deleted object during empty bucket operation', async () => { + const emptyTestBucketName = 'bucket-empty-webhook-test' + const authorization = `Bearer ${await serviceKeyAsync}` + + // Create a dedicated bucket for this test + await appInstance.inject({ + method: 'POST', + url: `/bucket`, + headers: { + authorization, + }, + payload: { + name: emptyTestBucketName, + }, + }) + + const objects = await Promise.all([ + createObject(pg, emptyTestBucketName), + createObject(pg, emptyTestBucketName), + createObject(pg, emptyTestBucketName), + ]) + + const response = await appInstance.inject({ + method: 'POST', + url: `/bucket/${emptyTestBucketName}/empty`, + headers: { + authorization, + }, + }) + + expect(response.statusCode).toBe(200) + + // Pass call invoked by empty on to the job handler to trigger the webhooks + expect(sendSpy).toHaveBeenCalledTimes(1) + const deleteJobCall = sendSpy.mock.calls[0][0] + expect(deleteJobCall.name).toBe(ObjectAdminDeleteAllBefore.queueName) + await ObjectAdminDeleteAllBefore.handle(deleteJobCall) + + // Check ObjectRemoved:Delete webhooks were sent as expected + expect(sendSpy).toHaveBeenCalledTimes(1 + objects.length) // 1 for the delete job + 3 for webhooks + objects.forEach((obj) => { + expect(sendSpy).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'webhooks', + options: expect.objectContaining({ + deadLetter: 'webhooks-dead-letter', + expireInSeconds: expect.any(Number), + }), + data: expect.objectContaining({ + $version: 'v1', + event: expect.objectContaining({ + $version: 'v1', + type: 'ObjectRemoved:Delete', + applyTime: expect.any(Number), + payload: expect.objectContaining({ + bucketId: emptyTestBucketName, + name: obj.name, + version: obj.version, + metadata: obj.metadata, + tenant: { + host: undefined, + ref: 'bjhaohmqunupljrqypxz', + }, + reqId: expect.any(String), + }), + }), + tenant: { + host: undefined, + ref: 'bjhaohmqunupljrqypxz', + }, + }), + }) + ) + }) + + // Clean up: delete the bucket + await appInstance.inject({ + method: 'DELETE', + url: `/bucket/${emptyTestBucketName}`, + headers: { + authorization, + }, + }) + }) }) async function createObject(pg: TenantConnection, bucketId: string) { - const objectName = Date.now() + const objectName = randomUUID() const tnx = await pg.transaction() const [data] = await tnx diff --git a/src/test/x-forwarded-host.test.ts b/src/test/x-forwarded-host.test.ts index 809491f7a..065d51dbd 100644 --- a/src/test/x-forwarded-host.test.ts +++ b/src/test/x-forwarded-host.test.ts @@ -1,8 +1,16 @@ -'use strict' +vi.hoisted(() => { + process.env.MULTI_TENANT = 'true' + process.env.IS_MULTITENANT = 'true' + process.env.REQUEST_X_FORWARDED_HOST_REGEXP = '^([a-z]{20})\\.supabase\\.(?:co|in|net)$' +}) + +import { signJWT } from '@internal/auth' +import { StorageKnexDB } from '@storage/database' import { getConfig, mergeConfig } from '../config' import * as tenant from '../internal/database/tenant' +import { adminApp } from './common' -jest.spyOn(tenant, 'getTenantConfig').mockImplementation(async () => ({ +vi.spyOn(tenant, 'getTenantConfig').mockImplementation(async () => ({ anonKey: process.env.ANON_KEY || '', databaseUrl: process.env.DATABASE_URL || '', serviceKey: process.env.SERVICE_KEY || '', @@ -31,42 +39,69 @@ jest.spyOn(tenant, 'getTenantConfig').mockImplementation(async () => ({ maxNamespaces: 30, maxTables: 20, }, + vectorBuckets: { + enabled: true, + maxBuckets: 5, + maxIndexes: 10, + }, }, })) // Mock module with inline implementation that doesn't depend on variables -jest.mock('@storage/database', () => ({ - StorageKnexDB: jest.fn().mockImplementation(() => ({ - listBuckets: jest.fn().mockResolvedValue([{ id: 'abc123', name: 'def456' }]), - })), +vi.mock('@storage/database', () => ({ + StorageKnexDB: vi.fn(function () { + return { + listBuckets: vi.fn().mockResolvedValue([{ id: 'abc123', name: 'def456' }]), + } + }), })) -// Access the mock after it's been created by the Jest runtime -const storageDbMock = require('@storage/database').StorageKnexDB - -// Use this reference in tests +const storageDbMock = vi.mocked(StorageKnexDB) +const fallbackTenantId = `x-forwarded-default-${Date.now()}` +const fallbackTenantJwtSecret = 'fallback-jwt-secret' +let fallbackAuthenticatedJwt = '' getConfig() mergeConfig({ isMultitenant: true, + tenantId: fallbackTenantId, requestXForwardedHostRegExp: '^([a-z]{20})\\.supabase\\.(?:co|in|net)$', }) -import { adminApp } from './common' import * as migrate from '../internal/database/migrations/migrate' import { multitenantKnex } from '../internal/database/multitenant-db' -import app from '../app' -import { FastifyInstance } from 'fastify' -let appInstance: FastifyInstance +let appInstance: import('fastify').FastifyInstance +let buildApp: typeof import('../app').default beforeAll(async () => { await migrate.runMultitenantMigrations() - jest.spyOn(migrate, 'runMigrationsOnTenant').mockResolvedValue() + vi.spyOn(migrate, 'runMigrationsOnTenant').mockResolvedValue() - jest - .spyOn(tenant, 'getServiceKey') - .mockResolvedValue(Promise.resolve(process.env.SERVICE_KEY || '')) + vi.spyOn(tenant, 'getServiceKey').mockResolvedValue(process.env.SERVICE_KEY || '') + + buildApp = (await import('../app')).default + + const fallbackTenantCreateResponse = await adminApp.inject({ + method: 'POST', + url: `/tenants/` + fallbackTenantId, + payload: { + anonKey: 'fallback-anon', + databaseUrl: 'fallback-db', + jwtSecret: fallbackTenantJwtSecret, + serviceKey: 'fallback-service', + }, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + expect(fallbackTenantCreateResponse.statusCode).toBe(201) + + fallbackAuthenticatedJwt = await signJWT( + { role: 'authenticated', sub: 'user-id' }, + fallbackTenantJwtSecret, + 100 + ) }) beforeEach(() => { @@ -74,7 +109,7 @@ beforeEach(() => { isMultitenant: true, requestXForwardedHostRegExp: '^([a-z]{20})\\.supabase\\.(?:co|in|net)$', }) - appInstance = app() + appInstance = buildApp() }) afterEach(async () => { @@ -82,14 +117,31 @@ afterEach(async () => { }) afterAll(async () => { + await adminApp.inject({ + method: 'DELETE', + url: '/tenants/' + fallbackTenantId, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + await adminApp.close() await multitenantKnex.destroy() - jest.restoreAllMocks() + vi.restoreAllMocks() }) describe('with X-Forwarded-Host header', () => { test('PostgREST URL is constructed using X-Forwarded-Host if regexp matches', async () => { const tenantId = 'abcdefghijklmnzzzzzz' const host = tenantId + '.supabase.co' + + await adminApp.inject({ + method: 'DELETE', + url: '/tenants/' + tenantId, + headers: { + apikey: process.env.ADMIN_API_KEYS, + }, + }) + const tenantCreateResponse = await adminApp.inject({ method: 'POST', url: `/tenants/` + tenantId, @@ -105,11 +157,13 @@ describe('with X-Forwarded-Host header', () => { }) expect(tenantCreateResponse.statusCode).toBe(201) + const authenticatedJwt = await signJWT({ role: 'authenticated', sub: 'user-id' }, 'c', 100) + const response = await appInstance.inject({ method: 'GET', url: `/bucket`, headers: { - authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, + authorization: `Bearer ${authenticatedJwt}`, 'x-forwarded-host': host, }, }) @@ -125,10 +179,17 @@ describe('with X-Forwarded-Host header', () => { // check that x-forwarded-host tenant id was passed all the way to the database correctly expect(storageDbMock).toHaveBeenCalledTimes(1) - expect(storageDbMock.mock.calls[0][0].options.tenantId).toBe(tenantId) - expect(storageDbMock.mock.calls[0][0].options.host).toBe(host) - expect(storageDbMock.mock.calls[0][1].tenantId).toBe(tenantId) - expect(storageDbMock.mock.calls[0][1].host).toBe(host) + + const [tenantConnectionArgs, tenantConnectionOptions] = storageDbMock.mock + .calls[0] as unknown as [ + { options: { tenantId: string; host: string } }, + { tenantId: string; host: string }, + ] + + expect(tenantConnectionArgs.options.tenantId).toBe(tenantId) + expect(tenantConnectionArgs.options.host).toBe(host) + expect(tenantConnectionOptions.tenantId).toBe(tenantId) + expect(tenantConnectionOptions.host).toBe(host) }) test('Error is thrown if X-Forwarded-Host is not present', async () => { @@ -136,7 +197,7 @@ describe('with X-Forwarded-Host header', () => { method: 'GET', url: `/bucket`, headers: { - authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, + authorization: `Bearer ${fallbackAuthenticatedJwt}`, }, }) expect(response.statusCode).toBe(400) @@ -149,7 +210,7 @@ describe('with X-Forwarded-Host header', () => { method: 'GET', url: `/bucket`, headers: { - authorization: `Bearer ${process.env.AUTHENTICATED_KEY}`, + authorization: `Bearer ${fallbackAuthenticatedJwt}`, 'x-forwarded-host': 'abcdefghijklmnopqrst.supabase.com', }, }) diff --git a/tsconfig.json b/tsconfig.json index cfb98e02b..7b2e88d1d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,10 @@ { "compilerOptions": { "paths": { - "@internal/*": ["./src/internal/*"], - "@storage/*": ["./src/storage/*"], + "@internal/*": ["./src/internal/*"], + "@storage/*": ["./src/storage/*"] }, + "types": ["node", "vitest/globals"], "downlevelIteration": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, @@ -13,16 +14,12 @@ "rootDir": "src", "moduleResolution": "node", "module": "commonjs", - "target": "ES2021", + "target": "ES2024", "outDir": "dist", "sourceMap": true, "strict": true, "skipLibCheck": true }, "include": ["./src/**/*"], - "exclude": ["node_modules", "dist"], - "parserOptions": { - "ecmaVersion": "2020", // Allows for the parsing of modern ECMAScript features - "sourceType": "module" // Allows for the use of imports - } + "exclude": ["node_modules", "dist"] } diff --git a/vitest.integration.config.ts b/vitest.integration.config.ts new file mode 100644 index 000000000..c2e7bb359 --- /dev/null +++ b/vitest.integration.config.ts @@ -0,0 +1,31 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { defineConfig } from 'vitest/config' +import IntegrationSequencer from './vitest.integration.sequencer' + +const rootDir = path.dirname(fileURLToPath(import.meta.url)) + +export default defineConfig({ + resolve: { + alias: { + '@internal': path.resolve(rootDir, 'src/internal'), + '@storage': path.resolve(rootDir, 'src/storage'), + }, + }, + test: { + coverage: { + provider: 'v8', + reporter: ['lcovonly', 'text-summary'], + }, + environment: 'node', + fileParallelism: false, + globals: true, + hookTimeout: 30_000, + include: ['src/test/**/*.test.ts'], + sequence: { + sequencer: IntegrationSequencer, + }, + setupFiles: ['src/test/vitest-setup.ts'], + testTimeout: 10_000, + }, +}) diff --git a/vitest.integration.sequencer.ts b/vitest.integration.sequencer.ts new file mode 100644 index 000000000..a6269a936 --- /dev/null +++ b/vitest.integration.sequencer.ts @@ -0,0 +1,24 @@ +import path from 'node:path' +import { BaseSequencer, type TestSpecification } from 'vitest/node' + +const testOrder = ['*', 'tus.test.ts', 's3-protocol.test.ts', 'rls.test.ts'] as const + +export default class IntegrationSequencer extends BaseSequencer { + async sort(files: TestSpecification[]): Promise { + const testBuckets = Object.fromEntries( + testOrder.map((entry) => [entry, [] as TestSpecification[]]) + ) as Record<(typeof testOrder)[number], TestSpecification[]> + + for (const file of files) { + const fileName = path.basename(file.moduleId) + const bucket = testBuckets[fileName as keyof typeof testBuckets] ?? testBuckets['*'] + bucket.push(file) + } + + const sortedBuckets = await Promise.all( + testOrder.map((entry) => super.sort(testBuckets[entry])) + ) + + return sortedBuckets.flat() + } +} diff --git a/vitest.unit.config.ts b/vitest.unit.config.ts new file mode 100644 index 000000000..58d421a25 --- /dev/null +++ b/vitest.unit.config.ts @@ -0,0 +1,24 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { defineConfig } from 'vitest/config' + +const rootDir = path.dirname(fileURLToPath(import.meta.url)) + +export default defineConfig({ + resolve: { + alias: { + '@internal': path.resolve(rootDir, 'src/internal'), + '@storage': path.resolve(rootDir, 'src/storage'), + }, + }, + test: { + coverage: { + provider: 'v8', + reporter: ['lcovonly', 'text-summary'], + }, + environment: 'node', + globals: true, + include: ['src/**/*.test.ts'], + exclude: ['src/test/**/*.test.ts'], + }, +}) diff --git a/watt.json b/watt.json new file mode 100644 index 000000000..6165739eb --- /dev/null +++ b/watt.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://schemas.platformatic.dev/@platformatic/node/3.52.0.json", + "node": { + "main": "./dist/start/server.js" + }, + "runtime": { + "exitOnUnhandledErrors": false, + "workers": "{WORKERS_NUM}", + "logger": { + "level": "{LOG_LEVEL}" + }, + "health": { + "enabled": "{WATT_HEALTH_ENABLED}", + "maxELU": "{WATT_HEALTH_MAX_ELU}", + "gracePeriod": "{WATT_HEALTH_GRACE_PERIOD}", + "maxUnhealthyChecks": "{WATT_HEALTH_MAX_UNHEALTHY_CHECKS}", + "interval": "{WATT_HEALTH_INTERVAL}" + }, + "managementApi": "{PLT_MANAGEMENT_API}" + } +}