diff --git a/README.md b/README.md index e6cf6ca..fcdd3fb 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ This repo contains the sample for [Keploy's](https://keploy.io) JS Application. 8. [Node-JWT](https://github.com/keploy/samples-typescript/tree/main/node-jwt) 9. [Node-Mongoose](https://github.com/keploy/samples-typescript/tree/main/node-mongoose) 10. [Rest-Express-Mongo](https://github.com/keploy/samples-typescript/tree/main/rest-express-mongo) +11. [Node-Dependency-Matrix](https://github.com/keploy/samples-typescript/tree/main/node-dependency-matrix) ## Community Support ❤️ diff --git a/node-dependency-matrix/.dockerignore b/node-dependency-matrix/.dockerignore new file mode 100644 index 0000000..a98267d --- /dev/null +++ b/node-dependency-matrix/.dockerignore @@ -0,0 +1,7 @@ +node_modules +dist +.generated +README.md +k8s +scripts +keploy diff --git a/node-dependency-matrix/.gitignore b/node-dependency-matrix/.gitignore new file mode 100644 index 0000000..e379bb9 --- /dev/null +++ b/node-dependency-matrix/.gitignore @@ -0,0 +1,4 @@ +dist +node_modules +.generated +keploy diff --git a/node-dependency-matrix/Dockerfile b/node-dependency-matrix/Dockerfile new file mode 100644 index 0000000..1bfd76b --- /dev/null +++ b/node-dependency-matrix/Dockerfile @@ -0,0 +1,33 @@ +FROM node:20-bookworm-slim@sha256:17281e8d1dc4d671976c6b89a12f47a44c2f390b63a989e2e327631041f544fd AS build + +WORKDIR /app + +COPY package*.json ./ +RUN npm ci + +COPY tsconfig.json ./ +COPY proto ./proto +COPY fixtures ./fixtures +COPY src ./src +COPY entrypoint.sh ./entrypoint.sh + +RUN npm run build && chmod +x /app/entrypoint.sh + +FROM node:20-bookworm-slim@sha256:17281e8d1dc4d671976c6b89a12f47a44c2f390b63a989e2e327631041f544fd + +WORKDIR /app + +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* + +COPY --from=build /app/node_modules ./node_modules +COPY --from=build /app/dist ./dist +COPY --from=build /app/proto ./proto +COPY --from=build /app/fixtures ./fixtures +COPY --from=build /app/entrypoint.sh ./entrypoint.sh + +ENV NODE_ENV=production +ENV PORT=8080 +ENV GRPC_PORT=9090 + +ENTRYPOINT ["./entrypoint.sh"] +CMD ["node", "dist/bin/app.js"] diff --git a/node-dependency-matrix/README.md b/node-dependency-matrix/README.md new file mode 100644 index 0000000..3429b94 --- /dev/null +++ b/node-dependency-matrix/README.md @@ -0,0 +1,105 @@ +# Node Dependency Matrix + +This sample is a TypeScript application built to validate Keploy cloud record/replay against a broad dependency matrix. + +What it exposes: + +- incoming HTTP +- incoming gRPC +- outgoing HTTPS +- outgoing HTTP/2 +- outgoing gRPC +- outgoing MySQL +- outgoing Postgres +- outgoing Mongo +- outgoing Redis +- outgoing Kafka +- outgoing SQS-over-HTTPS +- outgoing generic TLS traffic +- an async background workflow that performs real outbound calls after a `202 Accepted` +- a noisy endpoint +- an expected replay failure endpoint +- duplicate-friendly GET and POST endpoints for static dedup verification + +## Quick start + +```bash +cd samples-typescript/node-dependency-matrix +npm install +npm run build +bash k8s/deploy-kind.sh +APP_URL=http://localhost:30081 bash scripts/record_traffic.sh +``` + +If the default host ports are already occupied, override them: + +```bash +HOST_PROXY_PORT=31080 HOST_APP_PORT=31081 HOST_GRPC_PORT=31090 bash k8s/deploy-kind.sh +APP_URL=http://localhost:31081 bash scripts/record_traffic.sh +``` + +Optional incoming gRPC traffic: + +```bash +GRPC_TARGET=localhost:30090 bash scripts/send_grpc_traffic.sh +``` + +## Docker Compose + +For a non-Kubernetes local stack: + +```bash +cd samples-typescript/node-dependency-matrix +npm install +bash scripts/compose_up.sh +APP_URL=http://localhost:38081 bash scripts/record_traffic.sh +GRPC_TARGET=localhost:39090 bash scripts/send_grpc_traffic.sh +``` + +To stop it: + +```bash +bash scripts/compose_down.sh +``` + +If `38081` or `39090` are occupied: + +```bash +APP_HTTP_PORT=48081 APP_GRPC_PORT=49090 bash scripts/compose_up.sh +APP_URL=http://localhost:48081 bash scripts/record_traffic.sh +GRPC_TARGET=localhost:49090 bash scripts/send_grpc_traffic.sh +``` + +## Keploy expectations + +The machine-readable contract is: + +```bash +fixtures/expected-values.json +``` + +Use it later in manual verification or other automation for: + +- testcase counts +- required and acceptable mock kinds +- scenario-to-mock-kind mapping +- the exact HTTP and gRPC traffic plans +- the async workflow contract and expected background scenarios +- hosted UI + Helm expectations for Kind +- total mock count ranges +- dedup expectations +- expected replay pass/noisy/fail scenarios + +## Kubernetes + +The full Kind and hosted UI flow is in: + +```bash +k8s/README.md +``` + +The detailed step-by-step staging runbook and troubleshooting guide is in: + +```bash +k8s/STAGING_RUNBOOK.md +``` diff --git a/node-dependency-matrix/compose.yaml b/node-dependency-matrix/compose.yaml new file mode 100644 index 0000000..340bc10 --- /dev/null +++ b/node-dependency-matrix/compose.yaml @@ -0,0 +1,206 @@ +name: node-dependency-matrix + +x-node-matrix-image: &node_matrix_image + build: + context: . + image: node-dependency-matrix:compose + +services: + app: + <<: *node_matrix_image + depends_on: + fixture-service: + condition: service_started + mysql: + condition: service_healthy + postgres: + condition: service_healthy + mongo-tls: + condition: service_started + redis-tls: + condition: service_started + redpanda: + condition: service_started + sqs-tls: + condition: service_started + environment: + PORT: "8080" + GRPC_PORT: "9090" + FIXTURE_HTTPS_BASE: https://fixture-service:8443 + FIXTURE_HTTP2_ORIGIN: https://fixture-service:9443 + FIXTURE_GRPC_TARGET: fixture-service:50051 + FIXTURE_GENERIC_HOST: fixture-service + FIXTURE_GENERIC_PORT: "9445" + MYSQL_URL: mysql://root@mysql:3306/matrix + POSTGRES_URL: postgresql://postgres@postgres:5432/matrix + MONGO_URL: mongodb://mongo-tls:27017/matrix?tls=true + REDIS_URL: rediss://redis-tls:6380 + KAFKA_BROKERS: redpanda:9092 + KAFKA_TOPIC: matrix-events + SQS_ENDPOINT: https://sqs-tls:4567 + SQS_QUEUE_URL: https://sqs-tls:4567/000000000000/dependency-matrix + SAMPLE_CA_CERT_PATH: /etc/sample-certs/ca.crt + COMBINED_CA_CERT_PATH: /tmp/node-dependency-matrix-ca-bundle.crt + NODE_EXTRA_CA_CERTS: /tmp/node-dependency-matrix-ca-bundle.crt + SSL_CERT_FILE: /tmp/node-dependency-matrix-ca-bundle.crt + ports: + - "${APP_HTTP_PORT:-38081}:8080" + - "${APP_GRPC_PORT:-39090}:9090" + volumes: + - ./.generated/certs:/etc/sample-certs:ro + + fixture-service: + <<: *node_matrix_image + command: ["node", "dist/bin/dependencyFixture.js"] + environment: + TLS_CERT_PATH: /etc/sample-certs/proxy.crt + TLS_KEY_PATH: /etc/sample-certs/proxy.key + volumes: + - ./.generated/certs:/etc/sample-certs:ro + + mysql: + image: mysql:8.0 + command: ["mysqld", "--default-authentication-plugin=mysql_native_password"] + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: "yes" + MYSQL_DATABASE: matrix + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot"] + interval: 5s + timeout: 5s + retries: 20 + + postgres: + image: postgres:16 + environment: + POSTGRES_HOST_AUTH_METHOD: trust + POSTGRES_DB: matrix + POSTGRES_USER: postgres + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d matrix"] + interval: 5s + timeout: 5s + retries: 20 + + mongo: + image: mongo:7 + command: ["--bind_ip_all"] + + redis: + image: redis:7-alpine + + redpanda: + image: docker.redpanda.com/redpandadata/redpanda:v25.1.2 + command: + - redpanda + - start + - --overprovisioned + - --smp + - "1" + - --memory + - 512M + - --reserve-memory + - 0M + - --check=false + - --node-id + - "0" + - --kafka-addr + - PLAINTEXT://0.0.0.0:9092 + - --advertise-kafka-addr + - PLAINTEXT://redpanda:9092 + - --set + - redpanda.auto_create_topics_enabled=true + + localstack: + image: localstack/localstack:3.3 + environment: + SERVICES: sqs + AWS_DEFAULT_REGION: us-east-1 + volumes: + - ./scripts/localstack-init:/etc/localstack/init/ready.d:ro + + mysql-tls: + <<: *node_matrix_image + command: ["node", "dist/bin/tlsProxy.js"] + depends_on: + mysql: + condition: service_healthy + environment: + LISTEN_PORT: "3306" + TARGET_HOST: mysql + TARGET_PORT: "3306" + TLS_CERT_PATH: /etc/sample-certs/proxy.crt + TLS_KEY_PATH: /etc/sample-certs/proxy.key + volumes: + - ./.generated/certs:/etc/sample-certs:ro + + postgres-tls: + <<: *node_matrix_image + command: ["node", "dist/bin/tlsProxy.js"] + depends_on: + postgres: + condition: service_healthy + environment: + LISTEN_PORT: "5432" + TARGET_HOST: postgres + TARGET_PORT: "5432" + TLS_CERT_PATH: /etc/sample-certs/proxy.crt + TLS_KEY_PATH: /etc/sample-certs/proxy.key + volumes: + - ./.generated/certs:/etc/sample-certs:ro + + mongo-tls: + <<: *node_matrix_image + command: ["node", "dist/bin/tlsProxy.js"] + depends_on: + - mongo + environment: + LISTEN_PORT: "27017" + TARGET_HOST: mongo + TARGET_PORT: "27017" + TLS_CERT_PATH: /etc/sample-certs/proxy.crt + TLS_KEY_PATH: /etc/sample-certs/proxy.key + volumes: + - ./.generated/certs:/etc/sample-certs:ro + + redis-tls: + <<: *node_matrix_image + command: ["node", "dist/bin/tlsProxy.js"] + depends_on: + - redis + environment: + LISTEN_PORT: "6380" + TARGET_HOST: redis + TARGET_PORT: "6379" + TLS_CERT_PATH: /etc/sample-certs/proxy.crt + TLS_KEY_PATH: /etc/sample-certs/proxy.key + volumes: + - ./.generated/certs:/etc/sample-certs:ro + + kafka-tls: + <<: *node_matrix_image + command: ["node", "dist/bin/tlsProxy.js"] + depends_on: + - redpanda + environment: + LISTEN_PORT: "9094" + TARGET_HOST: redpanda + TARGET_PORT: "9092" + TLS_CERT_PATH: /etc/sample-certs/proxy.crt + TLS_KEY_PATH: /etc/sample-certs/proxy.key + volumes: + - ./.generated/certs:/etc/sample-certs:ro + + sqs-tls: + <<: *node_matrix_image + command: ["node", "dist/bin/tlsProxy.js"] + depends_on: + - localstack + environment: + LISTEN_PORT: "4567" + TARGET_HOST: localstack + TARGET_PORT: "4566" + TLS_CERT_PATH: /etc/sample-certs/proxy.crt + TLS_KEY_PATH: /etc/sample-certs/proxy.key + volumes: + - ./.generated/certs:/etc/sample-certs:ro diff --git a/node-dependency-matrix/entrypoint.sh b/node-dependency-matrix/entrypoint.sh new file mode 100755 index 0000000..c87afee --- /dev/null +++ b/node-dependency-matrix/entrypoint.sh @@ -0,0 +1,23 @@ +#!/bin/sh +set -eu + +COMBINED_CA_CERT_PATH="${COMBINED_CA_CERT_PATH:-/tmp/node-dependency-matrix-ca-bundle.crt}" + +rm -f "${COMBINED_CA_CERT_PATH}" + +if [ -n "${SAMPLE_CA_CERT_PATH:-}" ] && [ -f "${SAMPLE_CA_CERT_PATH}" ]; then + cat "${SAMPLE_CA_CERT_PATH}" > "${COMBINED_CA_CERT_PATH}" + cp "${SAMPLE_CA_CERT_PATH}" /usr/local/share/ca-certificates/node-dependency-matrix.crt + update-ca-certificates >/dev/null 2>&1 || true +fi + +if [ -f /tmp/keploy-tls/ca.crt ]; then + cat /tmp/keploy-tls/ca.crt >> "${COMBINED_CA_CERT_PATH}" +fi + +if [ ! -f "${COMBINED_CA_CERT_PATH}" ]; then + : > "${COMBINED_CA_CERT_PATH}" + >&2 echo "Warning: No CA certificates were found to create the combined CA bundle at '${COMBINED_CA_CERT_PATH}'. To fix this, either set SAMPLE_CA_CERT_PATH to a valid CA bundle file or mount /tmp/keploy-tls/ca.crt before starting this container so fixture endpoints can establish TLS correctly." +fi + +exec "$@" diff --git a/node-dependency-matrix/fixtures/expected-values.json b/node-dependency-matrix/fixtures/expected-values.json new file mode 100644 index 0000000..601c1b6 --- /dev/null +++ b/node-dependency-matrix/fixtures/expected-values.json @@ -0,0 +1,571 @@ +{ + "sampleId": "node-dependency-matrix", + "cluster": { + "namespace": "default", + "deployment": "node-dependency-matrix", + "service": "node-dependency-matrix", + "podLabelSelector": "app=node-dependency-matrix", + "httpServicePort": 8080, + "grpcServicePort": 9090, + "httpNodePort": 30081, + "grpcNodePort": 30090, + "k8sProxyNodePort": 30080 + }, + "coverage": { + "incoming": { + "httpEndpoints": [ + "/health", + "/expectations", + "/deps/http", + "/deps/http2", + "/deps/grpc", + "/deps/mysql", + "/deps/postgres", + "/deps/mongo", + "/deps/redis", + "/deps/kafka", + "/deps/sqs", + "/deps/generic", + "/deps/all", + "/dedup/catalog", + "/dedup/order", + "/async/catalog-sync", + "/async/catalog-sync/:jobId", + "/async/catalog-sync/:jobId/wait", + "/noise/runtime", + "/expected-fail/time-window" + ], + "grpcMethods": [ + "/dependencymatrix.DependencyMatrix/Ping", + "/dependencymatrix.DependencyMatrix/RunDependencyScenario" + ] + }, + "transportNotes": { + "incomingHttp": "plaintext HTTP on the sample service", + "incomingGrpc": "plaintext gRPC on the sample service", + "outgoingHttp": "HTTPS to fixture-service", + "outgoingHttp2": "TLS HTTP/2 to fixture-service", + "outgoingGrpc": "TLS gRPC to fixture-service", + "outgoingMySql": "direct MySQL protocol to mysql:3306", + "outgoingPostgres": "direct Postgres protocol to postgres:5432", + "outgoingMongo": "Mongo over TLS through mongo-tls", + "outgoingRedis": "Redis over TLS through redis-tls", + "outgoingKafka": "direct Kafka protocol to redpanda:9092", + "outgoingSqs": "HTTPS to sqs-tls", + "outgoingGeneric": "raw TLS socket to fixture-service:9445" + }, + "outgoingPreferredMockKinds": [ + "Http", + "Http2", + "gRPC", + "MySQL", + "PostgresV2", + "Mongo", + "Redis", + "Kafka", + "Generic" + ], + "enterpriseSpecificOutgoingIntegrations": [ + "Redis", + "Kafka", + "SQS" + ], + "knownNonGoals": [ + "incoming TLS gRPC is not exercised in the default flow", + "the HTTP ingress coverage now includes GET plus POST JSON and headers, but not multipart/form-data or auth/session traffic", + "the async workflow validates in-pod background dependency capture, not a separate worker deployment", + "mysql-tls, postgres-tls, and kafka-tls deployments exist but are not used by the default app path" + ] + }, + "hostedUi": { + "supportedEnvironments": { + "prod": { + "uiBaseUrl": "https://app.keploy.io", + "clustersPageUrl": "https://app.keploy.io/clusters", + "apiServerUrl": "https://api.keploy.io", + "environment": "prod" + }, + "staging": { + "uiBaseUrl": "https://app.staging.keploy.io", + "clustersPageUrl": "https://app.staging.keploy.io/clusters", + "apiServerUrl": "https://api.staging.keploy.io", + "environment": "staging" + } + }, + "requiredHelmValues": { + "service.type": "NodePort", + "service.nodePort": 30080, + "proxy.insecure.enabled": true + }, + "consistencyRules": [ + "use the same environment in the UI domain, apiServerUrl, and Helm environment value", + "use the same ingress URL in the UI cluster entry and keploy.ingressUrl" + ] + }, + "localAccess": { + "portForward": { + "script": "k8s/port-forward.sh", + "resource": "svc/node-dependency-matrix", + "http": { + "localPort": 8080, + "remotePort": 8080, + "baseUrl": "http://localhost:8080" + }, + "grpc": { + "localPort": 9090, + "remotePort": 9090, + "target": "localhost:9090" + } + } + }, + "recording": { + "pods": { + "expectedReplicas": 1, + "expectedReadyContainersWhileRecording": 2 + }, + "httpTrafficScript": { + "path": "scripts/record_traffic.sh", + "expectedTestcasesExact": 18, + "requests": [ + { + "method": "GET", + "path": "/deps/http", + "count": 1, + "preferredMockKinds": [ + "Http" + ], + "acceptableMockKinds": [ + "Http" + ], + "replayExpectation": "pass" + }, + { + "method": "GET", + "path": "/deps/http2", + "count": 1, + "preferredMockKinds": [ + "Http2" + ], + "acceptableMockKinds": [ + "Http2", + "Generic", + "Http" + ], + "replayExpectation": "pass" + }, + { + "method": "GET", + "path": "/deps/grpc", + "count": 1, + "preferredMockKinds": [ + "gRPC" + ], + "acceptableMockKinds": [ + "gRPC", + "Generic", + "Http2" + ], + "replayExpectation": "pass" + }, + { + "method": "GET", + "path": "/deps/mysql", + "count": 1, + "preferredMockKinds": [ + "MySQL" + ], + "acceptableMockKinds": [ + "MySQL" + ], + "replayExpectation": "pass" + }, + { + "method": "GET", + "path": "/deps/postgres", + "count": 1, + "preferredMockKinds": [ + "PostgresV2" + ], + "acceptableMockKinds": [ + "PostgresV2", + "Generic" + ], + "replayExpectation": "pass" + }, + { + "method": "GET", + "path": "/deps/mongo", + "count": 1, + "preferredMockKinds": [ + "Mongo" + ], + "acceptableMockKinds": [ + "Mongo", + "Generic" + ], + "replayExpectation": "pass" + }, + { + "method": "GET", + "path": "/deps/redis", + "count": 1, + "preferredMockKinds": [ + "Redis" + ], + "acceptableMockKinds": [ + "Redis", + "Generic" + ], + "replayExpectation": "pass" + }, + { + "method": "GET", + "path": "/deps/kafka", + "count": 1, + "preferredMockKinds": [ + "Kafka" + ], + "acceptableMockKinds": [ + "Kafka", + "Generic" + ], + "replayExpectation": "pass" + }, + { + "method": "GET", + "path": "/deps/sqs", + "count": 1, + "preferredMockKinds": [ + "Http" + ], + "acceptableMockKinds": [ + "Http", + "Generic" + ], + "replayExpectation": "pass" + }, + { + "method": "GET", + "path": "/deps/generic", + "count": 1, + "preferredMockKinds": [ + "Generic" + ], + "acceptableMockKinds": [ + "Generic" + ], + "replayExpectation": "pass" + }, + { + "method": "GET", + "path": "/dedup/catalog?id=alpha", + "count": 3, + "replayExpectation": "pass", + "dedupExpectation": { + "duplicateCount": 2 + } + }, + { + "method": "GET", + "path": "/dedup/catalog?id=beta", + "count": 2, + "replayExpectation": "pass", + "dedupExpectation": { + "duplicateCount": 1 + } + }, + { + "method": "POST", + "path": "/dedup/order", + "count": 3, + "matchHint": "x-matrix-dedup-key=order-alpha", + "replayExpectation": "pass", + "dedupExpectation": { + "duplicateCount": 2 + } + }, + { + "method": "POST", + "path": "/dedup/order", + "count": 1, + "matchHint": "x-matrix-dedup-key=order-beta", + "replayExpectation": "pass" + }, + { + "method": "POST", + "path": "/async/catalog-sync", + "count": 1, + "requestBody": { + "jobId": "sync-alpha", + "catalogId": "alpha" + }, + "replayExpectation": "pass", + "backgroundScenarios": [ + "deps-http", + "deps-redis", + "deps-sqs" + ] + }, + { + "method": "GET", + "path": "/async/catalog-sync/sync-alpha/wait?timeoutMs=5000", + "count": 1, + "replayExpectation": "pass" + }, + { + "method": "GET", + "path": "/noise/runtime", + "count": 1, + "replayExpectation": "noisy" + }, + { + "method": "GET", + "path": "/expected-fail/time-window?ts=", + "count": 1, + "replayExpectation": "fail" + } + ] + }, + "asyncWorkflows": [ + { + "scenario": "async/catalog-sync", + "jobId": "sync-alpha", + "catalogId": "alpha", + "startRequest": { + "method": "POST", + "path": "/async/catalog-sync" + }, + "completionRequest": { + "method": "GET", + "path": "/async/catalog-sync/sync-alpha/wait?timeoutMs=5000" + }, + "backgroundScenarios": [ + "deps-http", + "deps-redis", + "deps-sqs" + ], + "replayExpectation": "pass" + } + ], + "grpcTrafficScript": { + "path": "scripts/send_grpc_traffic.sh", + "expectedAdditionalTestcasesExact": 0, + "calls": [ + { + "method": "/dependencymatrix.DependencyMatrix/Ping", + "request": { + "name": "matrix" + }, + "expectedAdditionalTestcases": 1 + }, + { + "method": "/dependencymatrix.DependencyMatrix/RunDependencyScenario", + "request": { + "scenario": "deps-http" + }, + "expectedAdditionalTestcases": 1, + "invokesScenario": "deps-http" + } + ] + }, + "mockSummary": { + "total": { + "min": 9, + "max": 250 + }, + "requiredKinds": [ + "Http" + ], + "preferredKinds": [ + "Http", + "Http2", + "gRPC", + "MySQL", + "PostgresV2", + "Mongo", + "Redis", + "Kafka", + "Generic" + ], + "fallbackKinds": { + "deps-http2": ["Http2", "Generic", "Http"], + "deps-grpc": ["gRPC", "Generic", "Http2"], + "deps-postgres": ["PostgresV2", "Generic"], + "deps-mongo": ["Mongo", "Generic"], + "deps-redis": ["Redis", "Generic"], + "deps-kafka": ["Kafka", "Generic"], + "deps-sqs": ["Http", "Generic"] + } + }, + "dedup": { + "expectedEntries": [ + { + "method": "GET", + "path": "/dedup/catalog", + "duplicateCount": 4 + }, + { + "method": "POST", + "path": "/dedup/order", + "duplicateCount": 3 + } + ] + } + }, + "replay": { + "scenarioCatalog": [ + { + "scenario": "deps-http", + "triggerPath": "/deps/http", + "replayExpectation": "pass", + "acceptableMockKinds": [ + "Http" + ] + }, + { + "scenario": "deps-http2", + "triggerPath": "/deps/http2", + "replayExpectation": "pass", + "acceptableMockKinds": [ + "Http2", + "Generic", + "Http" + ] + }, + { + "scenario": "deps-grpc", + "triggerPath": "/deps/grpc", + "replayExpectation": "pass", + "acceptableMockKinds": [ + "gRPC", + "Generic", + "Http2" + ] + }, + { + "scenario": "deps-mysql", + "triggerPath": "/deps/mysql", + "replayExpectation": "pass", + "acceptableMockKinds": [ + "MySQL" + ] + }, + { + "scenario": "deps-postgres", + "triggerPath": "/deps/postgres", + "replayExpectation": "pass", + "acceptableMockKinds": [ + "PostgresV2", + "Generic" + ] + }, + { + "scenario": "deps-mongo", + "triggerPath": "/deps/mongo", + "replayExpectation": "pass", + "acceptableMockKinds": [ + "Mongo", + "Generic" + ] + }, + { + "scenario": "deps-redis", + "triggerPath": "/deps/redis", + "replayExpectation": "pass", + "acceptableMockKinds": [ + "Redis", + "Generic" + ] + }, + { + "scenario": "deps-kafka", + "triggerPath": "/deps/kafka", + "replayExpectation": "pass", + "acceptableMockKinds": [ + "Kafka", + "Generic" + ] + }, + { + "scenario": "deps-sqs", + "triggerPath": "/deps/sqs", + "replayExpectation": "pass", + "acceptableMockKinds": [ + "Http", + "Generic" + ] + }, + { + "scenario": "deps-generic", + "triggerPath": "/deps/generic", + "replayExpectation": "pass", + "acceptableMockKinds": [ + "Generic" + ] + }, + { + "scenario": "dedup/catalog", + "triggerPath": "/dedup/catalog", + "replayExpectation": "pass" + }, + { + "scenario": "dedup/order", + "triggerPath": "/dedup/order", + "replayExpectation": "pass" + }, + { + "scenario": "async/catalog-sync", + "triggerPath": "/async/catalog-sync", + "replayExpectation": "pass" + }, + { + "scenario": "async/catalog-sync/wait", + "triggerPath": "/async/catalog-sync/:jobId/wait", + "replayExpectation": "pass" + }, + { + "scenario": "noise/runtime", + "triggerPath": "/noise/runtime", + "replayExpectation": "noisy" + }, + { + "scenario": "expected-fail/time-window", + "triggerPath": "/expected-fail/time-window", + "replayExpectation": "fail" + } + ], + "expectedPassScenarios": [ + "deps-http", + "deps-http2", + "deps-grpc", + "deps-mysql", + "deps-postgres", + "deps-mongo", + "deps-redis", + "deps-kafka", + "deps-sqs", + "deps-generic", + "dedup/catalog", + "dedup/order", + "async/catalog-sync", + "async/catalog-sync/wait" + ], + "expectedNoisyScenarios": [ + "noise/runtime" + ], + "expectedFailScenarios": [ + "expected-fail/time-window" + ], + "environmentAgnostic": true, + "globalNoiseSuggestions": { + "body": [ + "runtime.requestId", + "runtime.servedAt" + ], + "header": [ + "x-matrix-generated-at", + "etag" + ] + } + } +} diff --git a/node-dependency-matrix/k8s/README.md b/node-dependency-matrix/k8s/README.md new file mode 100644 index 0000000..1198db1 --- /dev/null +++ b/node-dependency-matrix/k8s/README.md @@ -0,0 +1,312 @@ +# Kubernetes Setup + +This sample is the Kind-based regression target for Keploy Enterprise + `k8s-proxy` recording in Kubernetes. + +What it covers: + +- incoming HTTP testcases +- incoming gRPC testcases +- outgoing HTTPS +- outgoing HTTP/2 +- outgoing gRPC +- outgoing MySQL +- outgoing Postgres +- outgoing Mongo over TLS +- outgoing Redis over TLS +- outgoing Kafka +- outgoing SQS over HTTPS +- outgoing generic TLS traffic +- POST JSON + header-driven ingress cases +- an async background workflow that fans out to HTTP, Redis, and SQS after the initial response +- noisy replay cases +- expected replay failures +- static dedup-friendly duplicate GET and POST requests + +Transport notes: + +- incoming HTTP is plaintext on the sample service +- incoming gRPC is plaintext on the sample service +- outgoing HTTPS, HTTP/2, gRPC, Mongo, Redis, SQS, and the generic socket flow use TLS in the default path +- outgoing MySQL, Postgres, and Kafka use direct protocol connections in the default path +- MySQL is started with `mysql_native_password` because the current Keploy MySQL recorder does not handle the default MySQL 8 `caching_sha2_password` handshake reliably during recording +- Redis and the generic TLS socket keep TLS but skip hostname verification because the current Keploy TLS MITM path for those flows does not preserve SANs reliably during recording +- `mysql-tls`, `postgres-tls`, and `kafka-tls` are deployed for experimentation, but they are not exercised by the default app config today +- the async workflow stays inside one pod; it validates background dependency capture, not multi-workload consumer coordination + +The official Keploy Kind flow is documented at [Kubernetes Local Setup (Kind)](https://keploy.io/docs/keploy-cloud/kubernetes-local-setup/). The VM-specific hosted UI flow in `Run k8s-proxy ( with our ui ) with the kind in our vm’s.pdf` adds the hostname/TLS details you need when the cluster is running on a VM and the browser is on your laptop. + +## 1. Prerequisites + +Install and verify: + +- Docker +- `kind` +- `kubectl` +- Helm +- access to a hosted Keploy UI environment + +You do not need a local `enterprise-ui` or `api-server` for this flow. Use whichever hosted environment you want and keep the UI domain, `apiServerUrl`, and Helm `environment` value consistent. + +## 2. Choose the ingress mode before you deploy + +You need one ingress URL for `k8s-proxy`, and the exact same URL must be used in: + +- the hosted UI cluster entry +- the Helm value `keploy.ingressUrl` +- your browser trust flow + +### Option A: same machine as the browser + +If the browser and the Kind cluster are on the same machine, use: + +```text +http://localhost:30080 +``` + +This matches the official doc and requires `proxy.insecure.enabled=true`. + +### Option B: Kind is running inside a VM + +If the cluster is on a VM and you are opening the hosted UI from your laptop, use a hostname that resolves to the VM IP and open the proxy over HTTPS: + +```text +https://:30080 +``` + +Do this first on the laptop: + +1. Add an `/etc/hosts` entry that maps the VM IP to the hostname you will use. +2. Open `https://:30080` once after `k8s-proxy` is installed and accept the self-signed certificate warning. +3. If Chrome ignores your hosts entry because of secure DNS, disable secure DNS or use a browser that honors the local hosts mapping. + +The PDF examples use hostnames like `https://ayush.sharma.io:30080` and `https://yogesh.keploy.io:30080`. + +## 3. Create the Kind cluster and deploy the sample + +From `samples-typescript/node-dependency-matrix/`: + +```bash +bash k8s/deploy-kind.sh +``` + +If `30080`, `30081`, or `30090` are already taken: + +```bash +HOST_PROXY_PORT=31080 HOST_APP_PORT=31081 HOST_GRPC_PORT=31090 bash k8s/deploy-kind.sh +``` + +If you want the deploy script to print the VM/domain ingress URL instead of `localhost`, set it before running: + +```bash +KEPLOY_INGRESS_URL=https://matrix.keploy.vm:30080 bash k8s/deploy-kind.sh +``` + +Or let the script build it for you: + +```bash +KEPLOY_INGRESS_HOST=matrix.keploy.vm bash k8s/deploy-kind.sh +``` + +If you need to recreate the cluster: + +```bash +kind delete cluster --name node-dependency-matrix +bash k8s/deploy-kind.sh +``` + +What `k8s/deploy-kind.sh` already does: + +- creates the Kind cluster with NodePort mappings for `30080`, `30081`, and `30090` +- generates the sample CA and leaf certs +- builds and loads the `node-dependency-matrix` image into Kind +- deploys MySQL, Postgres, Mongo, Redis, Redpanda, LocalStack, the TLS fixtures, and the app + +## 4. Validate the sample before involving Keploy + +Run these on the machine where Kind is running: + +```bash +curl http://localhost:30081/health +APP_URL=http://localhost:30081 bash scripts/record_traffic.sh +GRPC_TARGET=localhost:30090 bash scripts/send_grpc_traffic.sh +``` + +If you changed the host ports, keep using the overridden app/gRPC ports in the traffic scripts. + +## 5. Choose the hosted Keploy environment + +Supported examples: + +- production UI: `https://app.keploy.io/clusters` +- staging UI: `https://app.staging.keploy.io/clusters` + +Use the matching API server and Helm `environment` value: + +- production: `keploy.apiServerUrl=https://api.keploy.io`, `environment=prod` +- staging: `keploy.apiServerUrl=https://api.staging.keploy.io`, `environment=staging` + +The sample is environment-agnostic. Pick one environment and keep it consistent end to end. + +## 6. Create the cluster in the hosted UI + +Open your chosen hosted clusters page and create a new cluster with: + +- cluster name: `node-dependency-matrix` +- ingress URL: + - `http://localhost:30080` for localhost mode + - `https://:30080` for VM/domain mode + +If you changed the proxy host port, replace `30080` with that port everywhere. + +## 7. Install `k8s-proxy` from the UI-generated Helm command + +Copy the Helm command generated by the hosted UI you chose, then verify that the effective values are: + +- `keploy.clusterName=node-dependency-matrix` +- `keploy.apiServerUrl=` +- `environment=` +- `keploy.ingressUrl=` +- `service.type=NodePort` +- `service.nodePort=30080` +- `proxy.insecure.enabled=true` + +Notes: + +- `proxy.insecure.enabled=true` is required for the `http://localhost:30080` NodePort flow from the official doc. +- For the VM/domain flow, keep the hostname exactly consistent between the hosted UI entry, the Helm value, and your `/etc/hosts` entry. +- Do not mix environments. For example, do not use `app.staging.keploy.io` with `https://api.keploy.io`, and do not use `environment=prod` with staging URLs. + +After Helm install: + +```bash +kubectl get pods -n keploy +kubectl logs -n keploy -l app=k8s-proxy --tail=100 -f +``` + +Healthy signals: + +- the `k8s-proxy` pod reaches `Running` +- no repeated `401` login failures +- the hosted UI cluster page turns `Active` +- the `default` namespace and `node-dependency-matrix` deployment are visible in the UI + +## 8. Start recording in the hosted UI + +Record this deployment: + +- namespace: `default` +- deployment: `node-dependency-matrix` + +When recording starts, the app pod should roll and become `2/2` because the Keploy sidecar gets injected. + +Watch it: + +```bash +kubectl get pods -w +``` + +## 9. Generate record-time traffic + +Once the hosted UI has started recording and the pod is `2/2`, port-forward the app service: + +```bash +bash k8s/port-forward.sh +``` + +Then, in a second terminal, run: + +```bash +APP_URL=http://localhost:8080 bash scripts/record_traffic.sh +GRPC_TARGET=localhost:9090 bash scripts/send_grpc_traffic.sh +``` + +If `8080` or `9090` are already in use locally: + +```bash +LOCAL_HTTP_PORT=18080 LOCAL_GRPC_PORT=19090 bash k8s/port-forward.sh +APP_URL=http://localhost:18080 bash scripts/record_traffic.sh +GRPC_TARGET=localhost:19090 bash scripts/send_grpc_traffic.sh +``` + +Expected record-time behavior: + +- the deployment restarts when recording begins +- the restarted pod becomes `2/2` +- the sample traffic goes through `kubectl port-forward`, not the service NodePorts +- the HTTP script creates exactly 23 HTTP testcases +- the gRPC script adds 2 incoming gRPC testcases +- the mock inventory includes the supported outgoing kinds, with acceptable fallbacks captured in the expectations file +- the async workflow finishes and records background HTTP, Redis, and SQS activity +- the noisy endpoint is reported as noisy during replay +- the time-window endpoint is an intentional replay failure + +## 10. Use the machine-readable contract for replay and validation + +The contract is in: + +```bash +fixtures/expected-values.json +``` + +It now includes: + +- the supported incoming and outgoing coverage matrix +- the hosted UI and Helm expectations +- the exact HTTP and gRPC traffic plans +- the async workflow request/response and background dependency plan +- preferred and acceptable mock kinds per scenario +- dedup expectations +- expected pass/noisy/fail replay outcomes + +The sample also exposes the same contract over HTTP: + +```bash +curl http://localhost:30081/expectations +``` + +That endpoint is the easiest source for future automation because it keeps the traffic plan and verification contract next to the sample itself. + +## 11. Local validation traffic path + +The existing cluster validation flow uses two separate network paths: + +- the browser talks to `k8s-proxy` through the cluster ingress URL +- the sample traffic itself is sent through `kubectl port-forward` to the app service + +This sample now documents that explicitly. For local validation, port-forward the app service instead of sending sample traffic to the proxy ingress: + +```bash +bash k8s/port-forward.sh +``` + +Then use: + +```bash +curl http://localhost:8080/health +APP_URL=http://localhost:8080 bash scripts/record_traffic.sh +GRPC_TARGET=localhost:9090 bash scripts/send_grpc_traffic.sh +``` + +The same ports and service name are also captured in `fixtures/expected-values.json`. + +## 12. Troubleshooting + +If the cluster never becomes `Active`: + +- verify the ingress URL in the hosted UI exactly matches the Helm value +- check `kubectl logs -n keploy -l app=k8s-proxy` +- confirm the access key in the Helm command is valid for the environment you chose +- confirm the UI domain, `apiServerUrl`, and Helm `environment` all point to the same environment + +If the laptop browser cannot reach the VM-hosted proxy: + +- verify the VM IP to hostname mapping in `/etc/hosts` +- make sure the Kind control-plane NodePort is mapped to `30080` +- confirm the service exists with `kubectl -n keploy get svc` +- visit the HTTPS ingress URL once and accept the certificate warning + +If recording starts but the app pod never reaches `2/2`: + +- check `kubectl describe pod ` +- check `kubectl logs deployment/node-dependency-matrix` +- verify the Keploy webhook and sidecar injection are healthy in the `k8s-proxy` logs diff --git a/node-dependency-matrix/k8s/STAGING_RUNBOOK.md b/node-dependency-matrix/k8s/STAGING_RUNBOOK.md new file mode 100644 index 0000000..6972ef3 --- /dev/null +++ b/node-dependency-matrix/k8s/STAGING_RUNBOOK.md @@ -0,0 +1,517 @@ +# Staging K8s Runbook + +This is the end-to-end runbook for using `node-dependency-matrix` with: + +- a local Kind cluster +- hosted Keploy staging UI +- `k8s-proxy` +- manual recording and replay validation + +This document is based on the exact flow that was used to make the sample work on a Mac. + +## Scope + +This runbook covers: + +- local tool setup +- sample deployment into Kind +- cluster registration in hosted staging UI +- `k8s-proxy` Helm install +- record-time traffic generation +- replay expectations +- troubleshooting for the real errors that came up while stabilizing the sample + +## 1. Use the right branch + +```bash +cd /Users/asish/coding/work/regression-test-kube-cloud-flow/samples-typescript +git fetch origin +git checkout codex/node-dependency-matrix +git pull +cd node-dependency-matrix +``` + +The branch that contains the working setup is: + +- `codex/node-dependency-matrix` + +## 2. Install and verify tools + +You need: + +- Docker Desktop +- `kubectl` +- `kind` +- Helm +- Node.js + npm + +On this Mac, tool lookup was sometimes inconsistent between shells. If commands are missing, use: + +```bash +export PATH=/opt/homebrew/bin:/usr/local/bin:/Applications/Docker.app/Contents/Resources/bin:$PATH +``` + +Verify: + +```bash +docker --version +kubectl version --client +kind --version +helm version +node --version +npm --version +``` + +## 3. Deploy the sample into Kind + +Default ports: + +- proxy host port: `30080` +- app HTTP port: `30081` +- app gRPC port: `30090` + +If those ports are free: + +```bash +bash k8s/deploy-kind.sh +``` + +If they are not free, use custom host ports. This is what was used during the working validation: + +```bash +HOST_PROXY_PORT=31080 HOST_APP_PORT=31081 HOST_GRPC_PORT=31090 bash k8s/deploy-kind.sh +``` + +What this does: + +- creates the Kind cluster +- generates sample certs +- builds and loads the sample image +- deploys MySQL, Postgres, Mongo, Redis, Redpanda, LocalStack, TLS fixtures, and the app + +## 4. Validate the sample before involving Keploy + +For the custom-port setup: + +```bash +curl http://localhost:31081/health +APP_URL=http://localhost:31081 bash scripts/record_traffic.sh +GRPC_TARGET=localhost:31090 bash scripts/send_grpc_traffic.sh +``` + +For default ports, replace `31081/31090` with `30081/30090`. + +Expected behavior: + +- `/health` returns JSON with `status: ok` +- `record_traffic.sh` runs all scenarios and exits `0` +- `send_grpc_traffic.sh` returns a `pong` response and a `deps-http` scenario result + +## 5. Open hosted staging UI + +Use the staging cluster UI: + +- [https://app.staging.keploy.io/clusters](https://app.staging.keploy.io/clusters) + +When you create the cluster entry, use: + +- cluster name: `node-dependency-matrix` +- ingress URL: `http://localhost:31080` if you used custom host port `31080` +- ingress URL: `http://localhost:30080` if you used the default host port + +Important: + +- the browser-facing ingress URL must match the host port on your Mac +- the chart `service.nodePort` inside Kind still stays `30080` + +## 6. Install `k8s-proxy` + +Do not rely blindly on the UI-generated Helm command. Validate the effective values. + +For staging, the chart must effectively use: + +- `keploy.accessKey=` +- `keploy.clusterName=node-dependency-matrix` +- `keploy.apiServerUrl=https://api.staging.keploy.io` +- `keploy.ingressUrl=http://localhost:31080` or `http://localhost:30080` +- `environment=staging` +- `service.type=NodePort` +- `service.nodePort=30080` +- `proxy.insecure.enabled=true` + +Example for the custom host-port flow: + +```bash +export PATH=/opt/homebrew/bin:/usr/local/bin:/Applications/Docker.app/Contents/Resources/bin:$PATH + +helm upgrade --install k8s-proxy oci://docker.io/keploy/k8s-proxy-chart --version 3.3.8 \ + --namespace keploy \ + --create-namespace \ + --set keploy.accessKey="" \ + --set keploy.clusterName="node-dependency-matrix" \ + --set keploy.apiServerUrl="https://api.staging.keploy.io" \ + --set keploy.ingressUrl="http://localhost:31080" \ + --set environment="staging" \ + --set service.type="NodePort" \ + --set service.nodePort="30080" \ + --set proxy.insecure.enabled="true" +``` + +Why this matters: + +- the chart uses nested `keploy.*` values +- the top-level `apiServerUrl` value is not enough +- without `service.type=NodePort`, the browser cannot reach the cluster agent +- without `proxy.insecure.enabled=true`, localhost browser access becomes a TLS/cert problem + +## 7. Verify `k8s-proxy` + +```bash +export PATH=/opt/homebrew/bin:/usr/local/bin:/Applications/Docker.app/Contents/Resources/bin:$PATH + +kubectl get pods -n keploy +kubectl get svc -n keploy +kubectl logs -n keploy -l app=k8s-proxy --tail=100 -f +``` + +Healthy signals: + +- the pod is `Running` +- the cluster page in staging becomes `Active` +- logs include `Initial Login successful` +- logs point to `https://api.staging.keploy.io` +- the service exposes `8081:30080/TCP` + +## 8. Start recording + +In staging UI: + +1. Open cluster `node-dependency-matrix` +2. Find deployment `default / node-dependency-matrix` +3. Click `Record` +4. Keep the normal defaults in the recording dialog +5. Start recording + +While recording starts: + +```bash +export PATH=/opt/homebrew/bin:/usr/local/bin:/Applications/Docker.app/Contents/Resources/bin:$PATH +kubectl get pods -n default -w +``` + +Expected behavior: + +- the app deployment restarts +- the new app pod becomes `2/2 Running` + +That `2/2` is the app container plus the Keploy sidecar. + +## 9. Send record-time traffic + +Once the hosted UI has started recording and the pod is `2/2 Running`, port-forward the app service: + +```bash +cd /Users/asish/coding/work/regression-test-kube-cloud-flow/samples-typescript/node-dependency-matrix +bash k8s/port-forward.sh +``` + +Then, in a second terminal: + +```bash +cd /Users/asish/coding/work/regression-test-kube-cloud-flow/samples-typescript/node-dependency-matrix +APP_URL=http://localhost:8080 bash scripts/record_traffic.sh +GRPC_TARGET=localhost:9090 bash scripts/send_grpc_traffic.sh +``` + +If `8080` or `9090` are already in use locally: + +```bash +cd /Users/asish/coding/work/regression-test-kube-cloud-flow/samples-typescript/node-dependency-matrix +LOCAL_HTTP_PORT=18080 LOCAL_GRPC_PORT=19090 bash k8s/port-forward.sh +APP_URL=http://localhost:18080 bash scripts/record_traffic.sh +GRPC_TARGET=localhost:19090 bash scripts/send_grpc_traffic.sh +``` + +Important: + +- after Keploy starts recording, do not send sample traffic to `30081/30090` or `31081/31090` +- keep using the hosted UI ingress URL only for the browser and `k8s-proxy` + +What this record-time traffic hits: + +- `/deps/http` +- `/deps/http2` +- `/deps/grpc` +- `/deps/mysql` +- `/deps/postgres` +- `/deps/mongo` +- `/deps/redis` +- `/deps/kafka` +- `/deps/sqs` +- `/deps/generic` +- GET and POST dedup endpoints +- async background-sync start and wait endpoints +- noisy endpoint +- expected replay-failure endpoint +- incoming gRPC endpoints + +The seed script now prints each scenario name before it runs, so failures are obvious. + +## 10. Stop recording and inspect the run + +In the UI: + +1. Stop recording +2. Open the captured recordings +3. Compare what you see with: + - [expected-values.json](/Users/asish/coding/work/regression-test-kube-cloud-flow/samples-typescript/node-dependency-matrix/fixtures/expected-values.json) + +What to verify: + +- 23 HTTP testcases and 2 incoming gRPC testcases were captured +- mock kinds are present for supported outgoing dependencies +- dedup captured repeated GET and POST requests +- the async workflow completed and captured its background HTTP, Redis, and SQS calls +- the noisy endpoint is recorded +- the time-window endpoint is recorded + +## 11. Trigger replay + +From the UI, start replay for the recorded run. + +Expected replay contract is already encoded in: + +- [expected-values.json](/Users/asish/coding/work/regression-test-kube-cloud-flow/samples-typescript/node-dependency-matrix/fixtures/expected-values.json) + +In broad terms: + +- core dependency flows should pass +- async catalog sync should pass +- `noise/runtime` should be noisy +- `expected-fail/time-window` is an intentional replay-failure case + +## 12. How to refresh the app after code changes + +If you change the sample and want that new code in the live Kind cluster: + +```bash +cd /Users/asish/coding/work/regression-test-kube-cloud-flow/samples-typescript/node-dependency-matrix +npm run build +docker build --pull=false -t node-dependency-matrix:latest . +kind load docker-image node-dependency-matrix:latest --name node-dependency-matrix +kubectl rollout restart deployment/node-dependency-matrix -n default +kubectl rollout status deployment/node-dependency-matrix -n default --timeout=240s +``` + +Wait until the new pod is back at `2/2` before sending more traffic during an active recording window. + +## 13. Troubleshooting + +### Error: `Login failed, retrying... status code 401` + +Symptoms: + +- proxy logs show `https://api.keploy.io/cluster/login` +- repeated `401` +- cluster never becomes active + +Cause: + +- prod/staging mismatch, or +- wrong access key, or +- wrong chart values path + +Fix: + +1. Make sure you are using staging UI if you want staging. +2. Use nested `keploy.*` values in Helm, not only top-level values. +3. Set: + - `keploy.apiServerUrl=https://api.staging.keploy.io` + - `environment=staging` +4. Create a fresh cluster in staging UI if the access key may be stale. + +### Error: cluster page shows active heartbeat but agent is not reachable + +Symptoms: + +- cluster page is active +- deployment list is broken or agent connection fails + +Cause: + +- wrong ingress URL or wrong host port + +Fix: + +1. Check what host port your Kind cluster actually mapped. +2. If you used `HOST_PROXY_PORT=31080`, the hosted UI cluster entry and Helm `keploy.ingressUrl` must both use `http://localhost:31080`. +3. Keep `service.nodePort=30080` inside the chart. + +### Error: browser cannot connect to `https://localhost:30080` + +Cause: + +- the secure listener needs browser trust + +Fix: + +- Prefer `proxy.insecure.enabled=true` with `http://localhost:` for local Mac + hosted UI. + +That is the setup used here. + +### Error: pod never becomes `2/2` after record starts + +Symptoms: + +- recording starts in UI +- app pod restarts or hangs incorrectly +- sidecar is not present + +Fix: + +```bash +kubectl get pods -n default +kubectl describe pod -n default +kubectl logs -n keploy -l app=k8s-proxy --tail=200 +``` + +You want to confirm the webhook and restart flow succeeded. + +### Error: MySQL request returns `500` during recording + +Symptoms: + +- `/deps/mysql` fails during recording +- Keploy logs mention `caching_sha2_password` + +Cause: + +- Keploy’s MySQL recorder does not reliably handle the default MySQL 8 `caching_sha2_password` handshake + +Fix already applied in this sample: + +- MySQL is started with `mysql_native_password` + +Files: + +- [01-mysql.yaml](/Users/asish/coding/work/regression-test-kube-cloud-flow/samples-typescript/node-dependency-matrix/k8s/manifests/01-mysql.yaml) +- [compose.yaml](/Users/asish/coding/work/regression-test-kube-cloud-flow/samples-typescript/node-dependency-matrix/compose.yaml) + +### Error: Redis request fails with `ERR_TLS_CERT_ALTNAME_INVALID` + +Symptoms: + +- `/deps/redis` fails +- app logs mention hostname mismatch for `redis-tls` + +Cause: + +- current Keploy TLS MITM path for Redis does not preserve SANs in a way that satisfies strict hostname verification + +Fix already applied in this sample: + +- keep TLS +- skip hostname verification for that Redis client + +File: + +- [dependencies.ts](/Users/asish/coding/work/regression-test-kube-cloud-flow/samples-typescript/node-dependency-matrix/src/lib/dependencies.ts) + +### Error: generic TLS request fails with hostname mismatch + +Symptoms: + +- `/deps/generic` returns `500` +- app logs mention `fixture-service` alt-name mismatch + +Cause: + +- same class of TLS MITM SAN mismatch as Redis + +Fix already applied in this sample: + +- keep TLS +- skip hostname verification for the generic TLS socket + +File: + +- [dependencies.ts](/Users/asish/coding/work/regression-test-kube-cloud-flow/samples-typescript/node-dependency-matrix/src/lib/dependencies.ts) + +### Error: Docker build hangs on `load metadata for docker.io/library/node:20-bookworm-slim` + +Symptoms: + +- Docker stalls on the base image metadata step +- BuildKit hangs before the real build starts + +Fix: + +1. The Dockerfile is pinned to the exact digest that was already working locally. +2. Build with: + +```bash +docker build --pull=false -t node-dependency-matrix:latest . +``` + +File: + +- [Dockerfile](/Users/asish/coding/work/regression-test-kube-cloud-flow/samples-typescript/node-dependency-matrix/Dockerfile) + +### Error: `kubectl`, `helm`, `docker`, `npm`, or `node` are not found + +Cause: + +- shell PATH does not include Homebrew or Docker Desktop CLI locations + +Fix: + +```bash +export PATH=/opt/homebrew/bin:/usr/local/bin:/Applications/Docker.app/Contents/Resources/bin:$PATH +``` + +## 14. Commands that were used successfully in the working setup + +Deploy: + +```bash +HOST_PROXY_PORT=31080 HOST_APP_PORT=31081 HOST_GRPC_PORT=31090 bash k8s/deploy-kind.sh +``` + +Validate sample: + +```bash +curl http://localhost:31081/health +APP_URL=http://localhost:31081 bash scripts/record_traffic.sh +GRPC_TARGET=localhost:31090 bash scripts/send_grpc_traffic.sh +``` + +Install `k8s-proxy` for staging: + +```bash +helm upgrade --install k8s-proxy oci://docker.io/keploy/k8s-proxy-chart --version 3.3.8 \ + --namespace keploy \ + --create-namespace \ + --set keploy.accessKey="" \ + --set keploy.clusterName="node-dependency-matrix" \ + --set keploy.apiServerUrl="https://api.staging.keploy.io" \ + --set keploy.ingressUrl="http://localhost:31080" \ + --set environment="staging" \ + --set service.type="NodePort" \ + --set service.nodePort="30080" \ + --set proxy.insecure.enabled="true" +``` + +Verify proxy: + +```bash +kubectl get pods -n keploy +kubectl get svc -n keploy +kubectl logs -n keploy -l app=k8s-proxy --tail=100 +``` + +## 15. Cleanup + +```bash +helm uninstall -n keploy k8s-proxy +kind delete cluster --name node-dependency-matrix +``` diff --git a/node-dependency-matrix/k8s/deploy-kind.sh b/node-dependency-matrix/k8s/deploy-kind.sh new file mode 100755 index 0000000..093bdb3 --- /dev/null +++ b/node-dependency-matrix/k8s/deploy-kind.sh @@ -0,0 +1,124 @@ +#!/bin/bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +CLUSTER_NAME="${KIND_CLUSTER_NAME:-node-dependency-matrix}" +KIND_CONTEXT="kind-${CLUSTER_NAME}" +KUBECTL=(kubectl --context "${KIND_CONTEXT}") +IMAGE_NAME="${IMAGE_NAME:-node-dependency-matrix:latest}" +KIND_NODE_IMAGE="${KIND_NODE_IMAGE:-}" +CERT_DIR="${ROOT_DIR}/.generated/certs" +HOST_PROXY_PORT="${HOST_PROXY_PORT:-30080}" +HOST_APP_PORT="${HOST_APP_PORT:-30081}" +HOST_GRPC_PORT="${HOST_GRPC_PORT:-30090}" +KEPLOY_INGRESS_URL="${KEPLOY_INGRESS_URL:-}" +KEPLOY_INGRESS_HOST="${KEPLOY_INGRESS_HOST:-localhost}" +KEPLOY_INGRESS_SCHEME="${KEPLOY_INGRESS_SCHEME:-}" +SKIP_DEPENDENCY_PULLS="${SKIP_DEPENDENCY_PULLS:-0}" + +DEPENDENCY_IMAGES=( + "mysql:8.0" + "postgres:16" + "mongo:7" + "redis:7-alpine" + "docker.redpanda.com/redpandadata/redpanda:v25.1.2" + "localstack/localstack:3.3" +) + +if ! kind get clusters | grep -qx "${CLUSTER_NAME}"; then + TMP_KIND_CONFIG="$(mktemp)" + cat > "${TMP_KIND_CONFIG}" </dev/null || true + done +fi + +docker build --pull=false -t "${IMAGE_NAME}" "${ROOT_DIR}" +kind load docker-image "${IMAGE_NAME}" --name "${CLUSTER_NAME}" + +"${KUBECTL[@]}" delete secret node-dependency-matrix-tls --ignore-not-found=true +"${KUBECTL[@]}" create secret generic node-dependency-matrix-tls \ + --from-file=ca.crt="${CERT_DIR}/ca.crt" \ + --from-file=proxy.crt="${CERT_DIR}/proxy.crt" \ + --from-file=proxy-fullchain.crt="${CERT_DIR}/proxy-fullchain.crt" \ + --from-file=proxy.key="${CERT_DIR}/proxy.key" + +"${KUBECTL[@]}" apply -f "${ROOT_DIR}/k8s/manifests" + +for tls_consumer in fixture-service mysql-tls postgres-tls mongo-tls redis-tls kafka-tls sqs-tls node-dependency-matrix; do + "${KUBECTL[@]}" rollout restart "deployment/${tls_consumer}" +done + +"${KUBECTL[@]}" rollout status deployment/mysql --timeout=180s +"${KUBECTL[@]}" rollout status deployment/postgres --timeout=180s +"${KUBECTL[@]}" rollout status deployment/mongo --timeout=180s +"${KUBECTL[@]}" rollout status deployment/redis --timeout=180s +"${KUBECTL[@]}" rollout status deployment/redpanda --timeout=180s +"${KUBECTL[@]}" rollout status deployment/localstack --timeout=180s +"${KUBECTL[@]}" rollout status deployment/fixture-service --timeout=180s +"${KUBECTL[@]}" rollout status deployment/mysql-tls --timeout=180s +"${KUBECTL[@]}" rollout status deployment/postgres-tls --timeout=180s +"${KUBECTL[@]}" rollout status deployment/mongo-tls --timeout=180s +"${KUBECTL[@]}" rollout status deployment/redis-tls --timeout=180s +"${KUBECTL[@]}" rollout status deployment/kafka-tls --timeout=180s +"${KUBECTL[@]}" rollout status deployment/sqs-tls --timeout=180s +"${KUBECTL[@]}" rollout status deployment/node-dependency-matrix --timeout=180s + +"${KUBECTL[@]}" get pods,svc + +if [[ -z "${KEPLOY_INGRESS_URL}" ]]; then + if [[ -z "${KEPLOY_INGRESS_SCHEME}" ]]; then + if [[ "${KEPLOY_INGRESS_HOST}" == "localhost" || "${KEPLOY_INGRESS_HOST}" == "127.0.0.1" ]]; then + KEPLOY_INGRESS_SCHEME="http" + else + KEPLOY_INGRESS_SCHEME="https" + fi + fi + + KEPLOY_INGRESS_URL="${KEPLOY_INGRESS_SCHEME}://${KEPLOY_INGRESS_HOST}:${HOST_PROXY_PORT}" +fi + +echo "" +echo "HTTP app: http://localhost:${HOST_APP_PORT}" +echo "gRPC app: localhost:${HOST_GRPC_PORT}" +echo "Keploy ingress URL: ${KEPLOY_INGRESS_URL}" +echo "Use that same ingress URL when you create the cluster in Keploy staging UI." diff --git a/node-dependency-matrix/k8s/kind-config.yaml b/node-dependency-matrix/k8s/kind-config.yaml new file mode 100644 index 0000000..45d945d --- /dev/null +++ b/node-dependency-matrix/k8s/kind-config.yaml @@ -0,0 +1,14 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: + - role: control-plane + extraPortMappings: + - containerPort: 30080 + hostPort: 30080 + protocol: TCP + - containerPort: 30081 + hostPort: 30081 + protocol: TCP + - containerPort: 30090 + hostPort: 30090 + protocol: TCP diff --git a/node-dependency-matrix/k8s/manifests/00-localstack.yaml b/node-dependency-matrix/k8s/manifests/00-localstack.yaml new file mode 100644 index 0000000..871a341 --- /dev/null +++ b/node-dependency-matrix/k8s/manifests/00-localstack.yaml @@ -0,0 +1,54 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: localstack-init-scripts +data: + 01-create-queue.sh: | + #!/bin/sh + set -eu + awslocal sqs create-queue --queue-name dependency-matrix >/dev/null 2>&1 || true +--- +apiVersion: v1 +kind: Service +metadata: + name: localstack +spec: + selector: + app: localstack + ports: + - name: edge + port: 4566 + targetPort: 4566 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: localstack +spec: + replicas: 1 + selector: + matchLabels: + app: localstack + template: + metadata: + labels: + app: localstack + spec: + containers: + - name: localstack + image: localstack/localstack:3.3 + env: + - name: SERVICES + value: sqs + - name: AWS_DEFAULT_REGION + value: us-east-1 + ports: + - containerPort: 4566 + volumeMounts: + - name: init-scripts + mountPath: /etc/localstack/init/ready.d + volumes: + - name: init-scripts + configMap: + name: localstack-init-scripts + defaultMode: 0755 diff --git a/node-dependency-matrix/k8s/manifests/01-mysql.yaml b/node-dependency-matrix/k8s/manifests/01-mysql.yaml new file mode 100644 index 0000000..82098f8 --- /dev/null +++ b/node-dependency-matrix/k8s/manifests/01-mysql.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +kind: Service +metadata: + name: mysql +spec: + selector: + app: mysql + ports: + - name: mysql + port: 3306 + targetPort: 3306 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mysql +spec: + replicas: 1 + selector: + matchLabels: + app: mysql + template: + metadata: + labels: + app: mysql + spec: + containers: + - name: mysql + image: mysql:8.0 + args: + - mysqld + - --default-authentication-plugin=mysql_native_password + env: + - name: MYSQL_ALLOW_EMPTY_PASSWORD + value: "yes" + - name: MYSQL_DATABASE + value: matrix + ports: + - containerPort: 3306 diff --git a/node-dependency-matrix/k8s/manifests/02-postgres.yaml b/node-dependency-matrix/k8s/manifests/02-postgres.yaml new file mode 100644 index 0000000..171eeee --- /dev/null +++ b/node-dependency-matrix/k8s/manifests/02-postgres.yaml @@ -0,0 +1,38 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgres +spec: + selector: + app: postgres + ports: + - name: postgres + port: 5432 + targetPort: 5432 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: postgres:16 + env: + - name: POSTGRES_HOST_AUTH_METHOD + value: trust + - name: POSTGRES_DB + value: matrix + - name: POSTGRES_USER + value: postgres + ports: + - containerPort: 5432 diff --git a/node-dependency-matrix/k8s/manifests/03-mongo.yaml b/node-dependency-matrix/k8s/manifests/03-mongo.yaml new file mode 100644 index 0000000..23234cc --- /dev/null +++ b/node-dependency-matrix/k8s/manifests/03-mongo.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +kind: Service +metadata: + name: mongo +spec: + selector: + app: mongo + ports: + - name: mongo + port: 27017 + targetPort: 27017 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mongo +spec: + replicas: 1 + selector: + matchLabels: + app: mongo + template: + metadata: + labels: + app: mongo + spec: + containers: + - name: mongo + image: mongo:7 + args: + - --bind_ip_all + ports: + - containerPort: 27017 diff --git a/node-dependency-matrix/k8s/manifests/04-redis.yaml b/node-dependency-matrix/k8s/manifests/04-redis.yaml new file mode 100644 index 0000000..4bc0236 --- /dev/null +++ b/node-dependency-matrix/k8s/manifests/04-redis.yaml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: Service +metadata: + name: redis +spec: + selector: + app: redis + ports: + - name: redis + port: 6379 + targetPort: 6379 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: redis:7-alpine + args: + - redis-server + - --appendonly + - "no" + - --save + - "" + ports: + - containerPort: 6379 diff --git a/node-dependency-matrix/k8s/manifests/05-redpanda.yaml b/node-dependency-matrix/k8s/manifests/05-redpanda.yaml new file mode 100644 index 0000000..888bb65 --- /dev/null +++ b/node-dependency-matrix/k8s/manifests/05-redpanda.yaml @@ -0,0 +1,50 @@ +apiVersion: v1 +kind: Service +metadata: + name: redpanda +spec: + selector: + app: redpanda + ports: + - name: kafka + port: 9092 + targetPort: 9092 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redpanda +spec: + replicas: 1 + selector: + matchLabels: + app: redpanda + template: + metadata: + labels: + app: redpanda + spec: + containers: + - name: redpanda + image: docker.redpanda.com/redpandadata/redpanda:v25.1.2 + args: + - redpanda + - start + - --overprovisioned + - --smp + - "1" + - --memory + - 512M + - --reserve-memory + - 0M + - --check=false + - --node-id + - "0" + - --kafka-addr + - PLAINTEXT://0.0.0.0:9092 + - --advertise-kafka-addr + - PLAINTEXT://redpanda:9092 + - --set + - redpanda.auto_create_topics_enabled=true + ports: + - containerPort: 9092 diff --git a/node-dependency-matrix/k8s/manifests/06-fixture-service.yaml b/node-dependency-matrix/k8s/manifests/06-fixture-service.yaml new file mode 100644 index 0000000..93acb31 --- /dev/null +++ b/node-dependency-matrix/k8s/manifests/06-fixture-service.yaml @@ -0,0 +1,58 @@ +apiVersion: v1 +kind: Service +metadata: + name: fixture-service +spec: + selector: + app: fixture-service + ports: + - name: https + port: 8443 + targetPort: 8443 + - name: http2 + port: 9443 + targetPort: 9443 + - name: grpc + port: 50051 + targetPort: 50051 + - name: generic + port: 9445 + targetPort: 9445 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fixture-service +spec: + replicas: 1 + selector: + matchLabels: + app: fixture-service + template: + metadata: + labels: + app: fixture-service + spec: + containers: + - name: fixture-service + image: node-dependency-matrix:latest + imagePullPolicy: IfNotPresent + command: ["node", "dist/bin/dependencyFixture.js"] + env: + - name: TLS_CERT_PATH + value: /etc/sample-certs/proxy.crt + - name: TLS_KEY_PATH + value: /etc/sample-certs/proxy.key + ports: + - containerPort: 8443 + - containerPort: 9443 + - containerPort: 50051 + - containerPort: 9445 + volumeMounts: + - name: sample-tls + mountPath: /etc/sample-certs + readOnly: true + volumes: + - name: sample-tls + secret: + secretName: node-dependency-matrix-tls diff --git a/node-dependency-matrix/k8s/manifests/07-tls-proxies.yaml b/node-dependency-matrix/k8s/manifests/07-tls-proxies.yaml new file mode 100644 index 0000000..418081e --- /dev/null +++ b/node-dependency-matrix/k8s/manifests/07-tls-proxies.yaml @@ -0,0 +1,297 @@ +apiVersion: v1 +kind: List +items: + - apiVersion: v1 + kind: Service + metadata: + name: mysql-tls + spec: + selector: + app: mysql-tls + ports: + - name: mysql + port: 3306 + targetPort: 3306 + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: mysql-tls + spec: + replicas: 1 + selector: + matchLabels: + app: mysql-tls + template: + metadata: + labels: + app: mysql-tls + spec: + containers: + - name: mysql-tls + image: node-dependency-matrix:latest + imagePullPolicy: IfNotPresent + command: ["node", "dist/bin/tlsProxy.js"] + env: + - name: LISTEN_PORT + value: "3306" + - name: TARGET_HOST + value: mysql + - name: TARGET_PORT + value: "3306" + - name: TLS_CERT_PATH + value: /etc/sample-certs/proxy.crt + - name: TLS_KEY_PATH + value: /etc/sample-certs/proxy.key + volumeMounts: + - name: sample-tls + mountPath: /etc/sample-certs + readOnly: true + volumes: + - name: sample-tls + secret: + secretName: node-dependency-matrix-tls + - apiVersion: v1 + kind: Service + metadata: + name: postgres-tls + spec: + selector: + app: postgres-tls + ports: + - name: postgres + port: 5432 + targetPort: 5432 + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: postgres-tls + spec: + replicas: 1 + selector: + matchLabels: + app: postgres-tls + template: + metadata: + labels: + app: postgres-tls + spec: + containers: + - name: postgres-tls + image: node-dependency-matrix:latest + imagePullPolicy: IfNotPresent + command: ["node", "dist/bin/tlsProxy.js"] + env: + - name: LISTEN_PORT + value: "5432" + - name: TARGET_HOST + value: postgres + - name: TARGET_PORT + value: "5432" + - name: TLS_CERT_PATH + value: /etc/sample-certs/proxy.crt + - name: TLS_KEY_PATH + value: /etc/sample-certs/proxy.key + volumeMounts: + - name: sample-tls + mountPath: /etc/sample-certs + readOnly: true + volumes: + - name: sample-tls + secret: + secretName: node-dependency-matrix-tls + - apiVersion: v1 + kind: Service + metadata: + name: mongo-tls + spec: + selector: + app: mongo-tls + ports: + - name: mongo + port: 27017 + targetPort: 27017 + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: mongo-tls + spec: + replicas: 1 + selector: + matchLabels: + app: mongo-tls + template: + metadata: + labels: + app: mongo-tls + spec: + containers: + - name: mongo-tls + image: node-dependency-matrix:latest + imagePullPolicy: IfNotPresent + command: ["node", "dist/bin/tlsProxy.js"] + env: + - name: LISTEN_PORT + value: "27017" + - name: TARGET_HOST + value: mongo + - name: TARGET_PORT + value: "27017" + - name: TLS_CERT_PATH + value: /etc/sample-certs/proxy.crt + - name: TLS_KEY_PATH + value: /etc/sample-certs/proxy.key + volumeMounts: + - name: sample-tls + mountPath: /etc/sample-certs + readOnly: true + volumes: + - name: sample-tls + secret: + secretName: node-dependency-matrix-tls + - apiVersion: v1 + kind: Service + metadata: + name: redis-tls + spec: + selector: + app: redis-tls + ports: + - name: redis + port: 6380 + targetPort: 6380 + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: redis-tls + spec: + replicas: 1 + selector: + matchLabels: + app: redis-tls + template: + metadata: + labels: + app: redis-tls + spec: + containers: + - name: redis-tls + image: node-dependency-matrix:latest + imagePullPolicy: IfNotPresent + command: ["node", "dist/bin/tlsProxy.js"] + env: + - name: LISTEN_PORT + value: "6380" + - name: TARGET_HOST + value: redis + - name: TARGET_PORT + value: "6379" + - name: TLS_CERT_PATH + value: /etc/sample-certs/proxy.crt + - name: TLS_KEY_PATH + value: /etc/sample-certs/proxy.key + volumeMounts: + - name: sample-tls + mountPath: /etc/sample-certs + readOnly: true + volumes: + - name: sample-tls + secret: + secretName: node-dependency-matrix-tls + - apiVersion: v1 + kind: Service + metadata: + name: kafka-tls + spec: + selector: + app: kafka-tls + ports: + - name: kafka + port: 9094 + targetPort: 9094 + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: kafka-tls + spec: + replicas: 1 + selector: + matchLabels: + app: kafka-tls + template: + metadata: + labels: + app: kafka-tls + spec: + containers: + - name: kafka-tls + image: node-dependency-matrix:latest + imagePullPolicy: IfNotPresent + command: ["node", "dist/bin/tlsProxy.js"] + env: + - name: LISTEN_PORT + value: "9094" + - name: TARGET_HOST + value: redpanda + - name: TARGET_PORT + value: "9092" + - name: TLS_CERT_PATH + value: /etc/sample-certs/proxy.crt + - name: TLS_KEY_PATH + value: /etc/sample-certs/proxy.key + volumeMounts: + - name: sample-tls + mountPath: /etc/sample-certs + readOnly: true + volumes: + - name: sample-tls + secret: + secretName: node-dependency-matrix-tls + - apiVersion: v1 + kind: Service + metadata: + name: sqs-tls + spec: + selector: + app: sqs-tls + ports: + - name: https + port: 4567 + targetPort: 4567 + - apiVersion: apps/v1 + kind: Deployment + metadata: + name: sqs-tls + spec: + replicas: 1 + selector: + matchLabels: + app: sqs-tls + template: + metadata: + labels: + app: sqs-tls + spec: + containers: + - name: sqs-tls + image: node-dependency-matrix:latest + imagePullPolicy: IfNotPresent + command: ["node", "dist/bin/tlsProxy.js"] + env: + - name: LISTEN_PORT + value: "4567" + - name: TARGET_HOST + value: localstack + - name: TARGET_PORT + value: "4566" + - name: TLS_CERT_PATH + value: /etc/sample-certs/proxy.crt + - name: TLS_KEY_PATH + value: /etc/sample-certs/proxy.key + volumeMounts: + - name: sample-tls + mountPath: /etc/sample-certs + readOnly: true + volumes: + - name: sample-tls + secret: + secretName: node-dependency-matrix-tls diff --git a/node-dependency-matrix/k8s/manifests/08-app.yaml b/node-dependency-matrix/k8s/manifests/08-app.yaml new file mode 100644 index 0000000..2393eaf --- /dev/null +++ b/node-dependency-matrix/k8s/manifests/08-app.yaml @@ -0,0 +1,98 @@ +apiVersion: v1 +kind: Service +metadata: + name: node-dependency-matrix +spec: + type: NodePort + selector: + app: node-dependency-matrix + ports: + - name: http + port: 8080 + targetPort: 8080 + nodePort: 30081 + - name: grpc + port: 9090 + targetPort: 9090 + nodePort: 30090 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: node-dependency-matrix +spec: + replicas: 1 + selector: + matchLabels: + app: node-dependency-matrix + template: + metadata: + labels: + app: node-dependency-matrix + spec: + containers: + - name: node-dependency-matrix + image: node-dependency-matrix:latest + imagePullPolicy: IfNotPresent + env: + - name: PORT + value: "8080" + - name: GRPC_PORT + value: "9090" + - name: FIXTURE_HTTPS_BASE + value: https://fixture-service:8443 + - name: FIXTURE_HTTP2_ORIGIN + value: https://fixture-service:9443 + - name: FIXTURE_GRPC_TARGET + value: fixture-service:50051 + - name: FIXTURE_GENERIC_HOST + value: fixture-service + - name: FIXTURE_GENERIC_PORT + value: "9445" + - name: MYSQL_URL + value: mysql://root@mysql:3306/matrix + - name: POSTGRES_URL + value: postgresql://postgres@postgres:5432/matrix + - name: MONGO_URL + value: mongodb://mongo-tls:27017/matrix?tls=true + - name: REDIS_URL + value: rediss://redis-tls:6380 + - name: KAFKA_BROKERS + value: redpanda:9092 + - name: KAFKA_TOPIC + value: matrix-events + - name: SQS_ENDPOINT + value: https://sqs-tls:4567 + - name: SQS_QUEUE_URL + value: https://sqs-tls:4567/000000000000/dependency-matrix + - name: SAMPLE_CA_CERT_PATH + value: /etc/sample-certs/ca.crt + - name: COMBINED_CA_CERT_PATH + value: /tmp/node-dependency-matrix-ca-bundle.crt + - name: NODE_EXTRA_CA_CERTS + value: /tmp/node-dependency-matrix-ca-bundle.crt + - name: SSL_CERT_FILE + value: /tmp/node-dependency-matrix-ca-bundle.crt + ports: + - containerPort: 8080 + - containerPort: 9090 + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 10 + volumeMounts: + - name: sample-tls + mountPath: /etc/sample-certs + readOnly: true + volumes: + - name: sample-tls + secret: + secretName: node-dependency-matrix-tls diff --git a/node-dependency-matrix/k8s/port-forward.sh b/node-dependency-matrix/k8s/port-forward.sh new file mode 100755 index 0000000..cf96738 --- /dev/null +++ b/node-dependency-matrix/k8s/port-forward.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -euo pipefail + +NAMESPACE="${NAMESPACE:-default}" +RESOURCE="${RESOURCE:-svc/node-dependency-matrix}" +LOCAL_HTTP_PORT="${LOCAL_HTTP_PORT:-8080}" +REMOTE_HTTP_PORT="${REMOTE_HTTP_PORT:-8080}" +LOCAL_GRPC_PORT="${LOCAL_GRPC_PORT:-9090}" +REMOTE_GRPC_PORT="${REMOTE_GRPC_PORT:-9090}" + +kubectl -n "${NAMESPACE}" port-forward "${RESOURCE}" \ + "${LOCAL_HTTP_PORT}:${REMOTE_HTTP_PORT}" \ + "${LOCAL_GRPC_PORT}:${REMOTE_GRPC_PORT}" diff --git a/node-dependency-matrix/package-lock.json b/node-dependency-matrix/package-lock.json new file mode 100644 index 0000000..0494c12 --- /dev/null +++ b/node-dependency-matrix/package-lock.json @@ -0,0 +1,3664 @@ +{ + "name": "node-dependency-matrix", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "node-dependency-matrix", + "version": "1.0.0", + "dependencies": { + "@aws-sdk/client-sqs": "^3.893.0", + "@grpc/grpc-js": "^1.14.0", + "@grpc/proto-loader": "^0.8.0", + "@smithy/node-http-handler": "^4.4.5", + "express": "^4.21.2", + "ioredis": "^5.8.1", + "kafkajs": "^2.2.4", + "mongodb": "^6.20.0", + "mysql2": "^3.15.3", + "pg": "^8.16.3" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^24.7.2", + "@types/pg": "^8.15.5", + "tsx": "^4.20.6", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-sqs": { + "version": "3.1015.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.1015.0.tgz", + "integrity": "sha512-j194LgVzJDydlWjVT+HBR3X5GGvEOFrv123ODWww29necxiq7wy4qFZm/2CF+X/OxQL5zA0gxuCgCqDJIk0UNw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/credential-provider-node": "^3.972.25", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.8", + "@aws-sdk/middleware-sdk-sqs": "^3.972.17", + "@aws-sdk/middleware-user-agent": "^3.972.25", + "@aws-sdk/region-config-resolver": "^3.972.9", + "@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.11", + "@smithy/config-resolver": "^4.4.13", + "@smithy/core": "^3.23.12", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-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.27", + "@smithy/middleware-retry": "^4.4.44", + "@smithy/middleware-serde": "^4.2.15", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.7", + "@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.43", + "@smithy/util-defaults-mode-node": "^4.2.47", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.973.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.24.tgz", + "integrity": "sha512-vvf82RYQu2GidWAuQq+uIzaPz9V0gSCXVqdVzRosgl5rXcspXOpSD3wFreGGW6AYymPr97Z69kjVnLePBxloDw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/xml-builder": "^3.972.15", + "@smithy/core": "^3.23.12", + "@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.7", + "@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": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.22.tgz", + "integrity": "sha512-cXp0VTDWT76p3hyK5D51yIKEfpf6/zsUvMfaB8CkyqadJxMQ8SbEeVroregmDlZbtG31wkj9ei0WnftmieggLg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.24.tgz", + "integrity": "sha512-h694K7+tRuepSRJr09wTvQfaEnjzsKZ5s7fbESrVds02GT/QzViJ94/HCNwM7bUfFxqpPXHxulZfL6Cou0dwPg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/types": "^3.973.6", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/node-http-handler": "^4.5.0", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "@smithy/util-stream": "^4.5.20", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.24.tgz", + "integrity": "sha512-O46fFmv0RDFWiWEA9/e6oW92BnsyAXuEgTTasxHligjn2RCr9L/DK773m/NoFaL3ZdNAUz8WxgxunleMnHAkeQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/credential-provider-env": "^3.972.22", + "@aws-sdk/credential-provider-http": "^3.972.24", + "@aws-sdk/credential-provider-login": "^3.972.24", + "@aws-sdk/credential-provider-process": "^3.972.22", + "@aws-sdk/credential-provider-sso": "^3.972.24", + "@aws-sdk/credential-provider-web-identity": "^3.972.24", + "@aws-sdk/nested-clients": "^3.996.14", + "@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": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.24.tgz", + "integrity": "sha512-sIk8oa6AzDoUhxsR11svZESqvzGuXesw62Rl2oW6wguZx8i9cdGCvkFg+h5K7iucUZP8wyWibUbJMc+J66cu5g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/nested-clients": "^3.996.14", + "@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": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.25.tgz", + "integrity": "sha512-m7dR0Dsva2P+VUpL+VkC0WwiDby5pgmWXkRVDB5rlwv0jXJrQJf7YMtCoM8Wjk0H9jPeCYOxOXXcIgp/qp5Alg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.22", + "@aws-sdk/credential-provider-http": "^3.972.24", + "@aws-sdk/credential-provider-ini": "^3.972.24", + "@aws-sdk/credential-provider-process": "^3.972.22", + "@aws-sdk/credential-provider-sso": "^3.972.24", + "@aws-sdk/credential-provider-web-identity": "^3.972.24", + "@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": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.22.tgz", + "integrity": "sha512-Os32s8/4gTZjBk5BtoS/cuTILaj+K72d0dVG7TCJX/fC4598cxwLDmf1AEHEpER5oL3K//yETjvFaz0V8oO5Xw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.24", + "@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": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.24.tgz", + "integrity": "sha512-PaFv7snEfypU2yXkpvfyWgddEbDLtgVe51wdZlinhc2doubBjUzJZZpgwuF2Jenl1FBydMhNpMjD6SBUM3qdSA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/nested-clients": "^3.996.14", + "@aws-sdk/token-providers": "3.1015.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": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.24.tgz", + "integrity": "sha512-J6H4R1nvr3uBTqD/EeIPAskrBtET4WFfNhpFySr2xW7bVZOXpQfPjrLSIx65jcNjBmLXzWq8QFLdVoGxiGG/SA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/nested-clients": "^3.996.14", + "@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": ">=20.0.0" + } + }, + "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": ">=20.0.0" + } + }, + "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.973.6", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.8.tgz", + "integrity": "sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA==", + "license": "Apache-2.0", + "dependencies": { + "@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": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-sqs": { + "version": "3.972.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.972.17.tgz", + "integrity": "sha512-LnzPRRoDXGtlFV2G1p2rsY6fRKrbf6Pvvc21KliSLw3+NmQca2+Aa1QIMRbpQvZYedsSqkGYwxe+qvXwQ2uxDw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.25.tgz", + "integrity": "sha512-QxiMPofvOt8SwSynTOmuZfvvPM1S9QfkESBxB22NMHTRXCJhR5BygLl8IXfC4jELiisQgwsgUby21GtXfX3f/g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@smithy/core": "^3.23.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-retry": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.996.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.14.tgz", + "integrity": "sha512-fSESKvh1VbfjtV3QMnRkCPZWkUbQof6T/DOpiLp33yP2wA+rbwwnZeG3XT3Ekljgw2I8X4XaQPnw+zSR8yxJ5Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.8", + "@aws-sdk/middleware-user-agent": "^3.972.25", + "@aws-sdk/region-config-resolver": "^3.972.9", + "@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.11", + "@smithy/config-resolver": "^4.4.13", + "@smithy/core": "^3.23.12", + "@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.27", + "@smithy/middleware-retry": "^4.4.44", + "@smithy/middleware-serde": "^4.2.15", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.5.0", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.7", + "@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.43", + "@smithy/util-defaults-mode-node": "^4.2.47", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.9.tgz", + "integrity": "sha512-eQ+dFU05ZRC/lC2XpYlYSPlXtX3VT8sn5toxN2Fv7EXlMoA2p9V7vUBKqHunfD4TRLpxUq8Y8Ol/nCqiv327Ng==", + "license": "Apache-2.0", + "dependencies": { + "@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": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1015.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1015.0.tgz", + "integrity": "sha512-3OSD4y110nisRhHzFOjoEeHU4GQL4KpzkX9PxzWaiZe0Yg2+thZKM0Pn9DjYwezH5JYfh/K++xK/SE0IHGrmCQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.24", + "@aws-sdk/nested-clients": "^3.996.14", + "@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": ">=20.0.0" + } + }, + "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.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "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": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "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": { + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.11.tgz", + "integrity": "sha512-1qdXbXo2s5MMLpUvw00284LsbhtlQ4ul7Zzdn5n+7p4WVgCMLqhxImpHIrjSoc72E/fyc4Wq8dLtUld2Gsh+lA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.25", + "@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": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.15", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.15.tgz", + "integrity": "sha512-PxMRlCFNiQnke9YR29vjFQwz4jq+6Q04rOVFeTDR2K7Qpv9h9FOWOxG+zJjageimYbWqE3bTuLjmryWHAWbvaA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "fast-xml-parser": "5.5.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "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/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "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==", + "license": "Apache-2.0", + "dependencies": { + "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": ">=6" + } + }, + "node_modules/@ioredis/commands": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz", + "integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz", + "integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "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==", + "license": "BSD-3-Clause" + }, + "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==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "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==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.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==", + "license": "BSD-3-Clause" + }, + "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==", + "license": "BSD-3-Clause" + }, + "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==", + "license": "BSD-3-Clause" + }, + "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==", + "license": "BSD-3-Clause" + }, + "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==", + "license": "BSD-3-Clause" + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.12.tgz", + "integrity": "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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": { + "@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": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.12", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.12.tgz", + "integrity": "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w==", + "license": "Apache-2.0", + "dependencies": { + "@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.20", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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": { + "@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" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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": { + "@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": ">=18.0.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" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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.0.0" + } + }, + "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": { + "@smithy/types": "^4.13.1", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.27", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.27.tgz", + "integrity": "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.12", + "@smithy/middleware-serde": "^4.2.15", + "@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": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.44", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.44.tgz", + "integrity": "sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA==", + "license": "Apache-2.0", + "dependencies": { + "@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.7", + "@smithy/types": "^4.13.1", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.15.tgz", + "integrity": "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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": { + "@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_modules/@smithy/node-http-handler": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.0.tgz", + "integrity": "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.12", + "@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/@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": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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.0.0" + } + }, + "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/@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": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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": { + "@smithy/types": "^4.13.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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": { + "@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": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.12.7", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.7.tgz", + "integrity": "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.12", + "@smithy/middleware-endpoint": "^4.4.27", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-stream": "^4.5.20", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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/@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": { + "@smithy/querystring-parser": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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": { + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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.0.0" + } + }, + "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": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.43", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.43.tgz", + "integrity": "sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.12", + "@smithy/smithy-client": "^4.12.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.47", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.47.tgz", + "integrity": "sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ==", + "license": "Apache-2.0", + "dependencies": { + "@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.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "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" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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.0.0" + } + }, + "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.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.12.tgz", + "integrity": "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.20", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.20.tgz", + "integrity": "sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/node-http-handler": "^4.5.0", + "@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.0.0" + } + }, + "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.0.0" + } + }, + "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/@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.0.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "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", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "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/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "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": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.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==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "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": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "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": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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/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": ">= 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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "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": ">=6" + } + }, + "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/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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": { + "path-expression-matcher": "^1.1.3" + } + }, + "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": { + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.2.0", + "strnum": "^2.2.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "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==", + "license": "ISC", + "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-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-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "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/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/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ioredis": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz", + "integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==", + "license": "MIT", + "dependencies": { + "@ioredis/commands": "1.5.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/ioredis/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": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ioredis/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/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/kafkajs": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/kafkajs/-/kafkajs-2.2.4.tgz", + "integrity": "sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA==", + "license": "MIT", + "engines": { + "node": ">=14.0.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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "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==", + "license": "Apache-2.0" + }, + "node_modules/lru.min": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "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/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mongodb": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.21.0.tgz", + "integrity": "sha512-URyb/VXMjJ4da46OeSXg+puO39XH9DeQpWCslifrRn9JWugy0D+DvvBvkm2WxmHe61O/H19JM66p1z7RHVkZ6A==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.2" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.3.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/mysql2": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.20.0.tgz", + "integrity": "sha512-eCLUs7BNbgA6nf/MZXsaBO1SfGs0LtLVrJD3WeWq+jPLDWkSufTD+aGMwykfUVPdZnblaUK1a8G/P63cl9FkKg==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.2", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.2", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.3.3" + }, + "engines": { + "node": ">= 8.0" + }, + "peerDependencies": { + "@types/node": ">= 8" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "license": "MIT", + "dependencies": { + "lru.min": "^1.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz", + "integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.12.0", + "pg-pool": "^3.13.0", + "pg-protocol": "^1.13.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.3.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", + "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz", + "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz", + "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", + "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "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-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "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" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/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/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sql-escaper": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz", + "integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=2.0.0", + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" + } + }, + "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==", + "license": "MIT" + }, + "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": ">= 0.8" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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/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": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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/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/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "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==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "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==", + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/node-dependency-matrix/package.json b/node-dependency-matrix/package.json new file mode 100644 index 0000000..8b583c8 --- /dev/null +++ b/node-dependency-matrix/package.json @@ -0,0 +1,37 @@ +{ + "name": "node-dependency-matrix", + "version": "1.0.0", + "private": true, + "description": "TypeScript sample app for exercising Keploy cloud record/replay dependency flows.", + "main": "dist/bin/app.js", + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node dist/bin/app.js", + "start:app": "node dist/bin/app.js", + "start:fixture": "node dist/bin/dependencyFixture.js", + "start:tls-proxy": "node dist/bin/tlsProxy.js", + "dev": "tsx watch src/bin/app.ts" + }, + "engines": { + "node": ">=20" + }, + "dependencies": { + "@aws-sdk/client-sqs": "^3.893.0", + "@grpc/grpc-js": "^1.14.0", + "@grpc/proto-loader": "^0.8.0", + "@smithy/node-http-handler": "^4.4.5", + "express": "^4.21.2", + "ioredis": "^5.8.1", + "kafkajs": "^2.2.4", + "mongodb": "^6.20.0", + "mysql2": "^3.15.3", + "pg": "^8.16.3" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^24.7.2", + "@types/pg": "^8.15.5", + "tsx": "^4.20.6", + "typescript": "^5.9.3" + } +} diff --git a/node-dependency-matrix/proto/dependency_matrix.proto b/node-dependency-matrix/proto/dependency_matrix.proto new file mode 100644 index 0000000..2c37110 --- /dev/null +++ b/node-dependency-matrix/proto/dependency_matrix.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +package dependencymatrix; + +service DependencyFixture { + rpc GetDependencyQuote (QuoteRequest) returns (QuoteReply); +} + +service DependencyMatrix { + rpc Ping (PingRequest) returns (PingReply); + rpc RunDependencyScenario (ScenarioRequest) returns (ScenarioReply); +} + +message QuoteRequest { + string scenario = 1; +} + +message QuoteReply { + string scenario = 1; + string message = 2; + int64 unix_time = 3; +} + +message PingRequest { + string name = 1; +} + +message PingReply { + string message = 1; + string sample_id = 2; +} + +message ScenarioRequest { + string scenario = 1; +} + +message ScenarioReply { + string scenario = 1; + string status = 2; + string payload_json = 3; +} diff --git a/node-dependency-matrix/scripts/compose_down.sh b/node-dependency-matrix/scripts/compose_down.sh new file mode 100755 index 0000000..fa270e1 --- /dev/null +++ b/node-dependency-matrix/scripts/compose_down.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +docker compose -f "${ROOT_DIR}/compose.yaml" down -v "$@" diff --git a/node-dependency-matrix/scripts/compose_up.sh b/node-dependency-matrix/scripts/compose_up.sh new file mode 100755 index 0000000..8a781dc --- /dev/null +++ b/node-dependency-matrix/scripts/compose_up.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +bash "${ROOT_DIR}/scripts/generate_certs.sh" "${ROOT_DIR}/.generated/certs" + +docker compose -f "${ROOT_DIR}/compose.yaml" up --build -d "$@" + +echo "" +echo "Compose app HTTP: http://localhost:${APP_HTTP_PORT:-38081}" +echo "Compose app gRPC: localhost:${APP_GRPC_PORT:-39090}" diff --git a/node-dependency-matrix/scripts/generate_certs.sh b/node-dependency-matrix/scripts/generate_certs.sh new file mode 100755 index 0000000..adbbdae --- /dev/null +++ b/node-dependency-matrix/scripts/generate_certs.sh @@ -0,0 +1,63 @@ +#!/bin/bash +set -euo pipefail + +OUTPUT_DIR="${1:-.generated/certs}" +mkdir -p "${OUTPUT_DIR}" + +CA_CONFIG="${OUTPUT_DIR}/ca.cnf" +SERVER_CONFIG="${OUTPUT_DIR}/server.cnf" + +cat > "${CA_CONFIG}" <<'EOF' +[req] +distinguished_name = req_distinguished_name +x509_extensions = v3_ca +prompt = no + +[req_distinguished_name] +CN = node-dependency-matrix-ca + +[v3_ca] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = critical, CA:true +keyUsage = critical, keyCertSign, cRLSign +EOF + +cat > "${SERVER_CONFIG}" <<'EOF' +[req] +distinguished_name = req_distinguished_name +req_extensions = v3_server +prompt = no + +[req_distinguished_name] +CN = node-dependency-matrix + +[v3_server] +basicConstraints = critical, CA:false +keyUsage = critical, digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth +subjectAltName = @alt_names + +[alt_names] +DNS.1 = fixture-service +DNS.2 = mysql-tls +DNS.3 = postgres-tls +DNS.4 = mongo-tls +DNS.5 = redis-tls +DNS.6 = kafka-tls +DNS.7 = sqs-tls +DNS.8 = localhost +IP.1 = 127.0.0.1 +EOF + +openssl genrsa -out "${OUTPUT_DIR}/ca.key" 2048 >/dev/null 2>&1 +openssl req -x509 -new -nodes -key "${OUTPUT_DIR}/ca.key" -sha256 -days 3650 -out "${OUTPUT_DIR}/ca.crt" -config "${CA_CONFIG}" >/dev/null 2>&1 + +openssl genrsa -out "${OUTPUT_DIR}/proxy.key" 2048 >/dev/null 2>&1 +openssl req -new -key "${OUTPUT_DIR}/proxy.key" -out "${OUTPUT_DIR}/proxy.csr" -config "${SERVER_CONFIG}" >/dev/null 2>&1 +openssl x509 -req -in "${OUTPUT_DIR}/proxy.csr" -CA "${OUTPUT_DIR}/ca.crt" -CAkey "${OUTPUT_DIR}/ca.key" -CAcreateserial -out "${OUTPUT_DIR}/proxy.crt" -days 3650 -sha256 -extensions v3_server -extfile "${SERVER_CONFIG}" >/dev/null 2>&1 +cat "${OUTPUT_DIR}/proxy.crt" "${OUTPUT_DIR}/ca.crt" > "${OUTPUT_DIR}/proxy-fullchain.crt" + +rm -f "${OUTPUT_DIR}/proxy.csr" "${OUTPUT_DIR}/ca.srl" "${CA_CONFIG}" "${SERVER_CONFIG}" + +echo "Generated certs in ${OUTPUT_DIR}" diff --git a/node-dependency-matrix/scripts/localstack-init/01-create-queue.sh b/node-dependency-matrix/scripts/localstack-init/01-create-queue.sh new file mode 100755 index 0000000..b9409b3 --- /dev/null +++ b/node-dependency-matrix/scripts/localstack-init/01-create-queue.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +awslocal sqs create-queue --queue-name dependency-matrix >/dev/null 2>&1 || true diff --git a/node-dependency-matrix/scripts/record_traffic.sh b/node-dependency-matrix/scripts/record_traffic.sh new file mode 100755 index 0000000..ae277cf --- /dev/null +++ b/node-dependency-matrix/scripts/record_traffic.sh @@ -0,0 +1,74 @@ +#!/bin/bash +set -euo pipefail + +APP_URL="${APP_URL:-http://localhost:30081}" + +echo "Using APP_URL=${APP_URL}" + +show_recording_hint() { + cat >&2 <<'EOF' +Hint: if this target is a Keploy-hosted recording pod in Kubernetes, send sample traffic through port-forward instead of the app NodePorts: + kubectl -n default port-forward svc/node-dependency-matrix 8080:8080 9090:9090 + APP_URL=http://localhost:8080 bash scripts/record_traffic.sh +EOF +} + +preflight() { + local expectations_status + expectations_status="$(curl -sS -o /dev/null -w '%{http_code}' "${APP_URL}/expectations" || true)" + if [[ "${expectations_status}" == "200" ]]; then + return + fi + + echo "Preflight failed: ${APP_URL}/expectations returned ${expectations_status:-connection-error}." >&2 + if [[ "${APP_URL}" =~ localhost:(30081|31081)$ ]]; then + show_recording_hint + fi + exit 1 +} + +run() { + local scenario="$1" + local path="$2" + echo + echo "==> ${scenario}" + curl -fsS "${APP_URL}${path}" + echo +} + +post_json() { + local scenario="$1" + local path="$2" + local body="$3" + shift 3 + echo + echo "==> ${scenario}" + curl -fsS -X POST "${APP_URL}${path}" -H "content-type: application/json" "$@" --data "${body}" + echo +} + +preflight + +run "deps-http" "/deps/http" +run "deps-http2" "/deps/http2" +run "deps-grpc" "/deps/grpc" +run "deps-mysql" "/deps/mysql" +run "deps-postgres" "/deps/postgres" +run "deps-mongo" "/deps/mongo" +run "deps-redis" "/deps/redis" +run "deps-kafka" "/deps/kafka" +run "deps-sqs" "/deps/sqs" +run "deps-generic" "/deps/generic" +run "dedup-alpha-1" "/dedup/catalog?id=alpha" +run "dedup-alpha-2" "/dedup/catalog?id=alpha" +run "dedup-alpha-3" "/dedup/catalog?id=alpha" +run "dedup-beta-1" "/dedup/catalog?id=beta" +run "dedup-beta-2" "/dedup/catalog?id=beta" +post_json "dedup-order-alpha-1" "/dedup/order" '{"customerTier":"gold","items":[{"quantity":2,"sku":"sku-2"},{"sku":"sku-1","quantity":1}],"orderId":"order-alpha"}' -H "x-matrix-dedup-key: order-alpha" +post_json "dedup-order-alpha-2" "/dedup/order" '{"orderId":"order-alpha","items":[{"sku":"sku-1","quantity":1},{"sku":"sku-2","quantity":2}],"customerTier":"gold"}' -H "x-matrix-dedup-key: order-alpha" +post_json "dedup-order-alpha-3" "/dedup/order" '{"items":[{"sku":"sku-2","quantity":2},{"quantity":1,"sku":"sku-1"}],"customerTier":"gold","orderId":"order-alpha"}' -H "x-matrix-dedup-key: order-alpha" +post_json "dedup-order-beta-1" "/dedup/order" '{"customerTier":"silver","items":[{"sku":"sku-3","quantity":1}],"orderId":"order-beta"}' -H "x-matrix-dedup-key: order-beta" +post_json "async-catalog-sync-start" "/async/catalog-sync" '{"jobId":"sync-alpha","catalogId":"alpha"}' -H "x-matrix-correlation-id: sync-alpha" +run "async-catalog-sync-wait" "/async/catalog-sync/sync-alpha/wait?timeoutMs=5000" +run "noise-runtime" "/noise/runtime" +run "expected-fail-time-window" "/expected-fail/time-window?ts=$(date +%s)" diff --git a/node-dependency-matrix/scripts/send_grpc_traffic.sh b/node-dependency-matrix/scripts/send_grpc_traffic.sh new file mode 100755 index 0000000..6b161c6 --- /dev/null +++ b/node-dependency-matrix/scripts/send_grpc_traffic.sh @@ -0,0 +1,59 @@ +#!/bin/bash +set -euo pipefail + +GRPC_TARGET="${GRPC_TARGET:-localhost:30090}" + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +show_recording_hint() { + cat >&2 <<'EOF' +Hint: if this target is a Keploy-hosted recording pod in Kubernetes, send sample traffic through port-forward instead of the app NodePorts: + kubectl -n default port-forward svc/node-dependency-matrix 8080:8080 9090:9090 + GRPC_TARGET=localhost:9090 bash scripts/send_grpc_traffic.sh +EOF +} + +if ! output="$( + GRPC_TARGET="${GRPC_TARGET}" ROOT_DIR="${ROOT_DIR}" node <<'NODE' 2>&1 +const path = require('path'); +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); + +const rootDir = process.env.ROOT_DIR; +const target = process.env.GRPC_TARGET; +const protoPath = path.join(rootDir, 'proto', 'dependency_matrix.proto'); +const packageDefinition = protoLoader.loadSync(protoPath, { + keepCase: false, + longs: String, + enums: String, + defaults: true, + oneofs: true +}); +const proto = grpc.loadPackageDefinition(packageDefinition).dependencymatrix; +const client = new proto.DependencyMatrix(target, grpc.credentials.createInsecure()); + +client.Ping({ name: 'matrix' }, (pingErr, pingRes) => { + if (pingErr) { + console.error(pingErr); + process.exit(1); + } + console.log(JSON.stringify(pingRes)); + client.RunDependencyScenario({ scenario: 'deps-http' }, (scenarioErr, scenarioRes) => { + if (scenarioErr) { + console.error(scenarioErr); + process.exit(1); + } + console.log(JSON.stringify(scenarioRes)); + client.close(); + }); +}); +NODE + )"; then + printf '%s\n' "${output}" >&2 + if [[ "${output}" == *"ECONNREFUSED"* ]] && [[ "${GRPC_TARGET}" =~ localhost:(30090|31090)$ ]]; then + show_recording_hint + fi + exit 1 +fi + +printf '%s\n' "${output}" diff --git a/node-dependency-matrix/src/bin/app.ts b/node-dependency-matrix/src/bin/app.ts new file mode 100644 index 0000000..34a62d3 --- /dev/null +++ b/node-dependency-matrix/src/bin/app.ts @@ -0,0 +1,389 @@ +import * as grpc from '@grpc/grpc-js'; +import express, { Response } from 'express'; + +import { getCatalogSyncJob, startCatalogSyncJob, waitForCatalogSyncJob } from '../lib/asyncJobs'; +import { loadConfig, readExpectationsJson } from '../lib/config'; +import { + runAllDependencyScenarios, + runGenericScenario, + runGrpcScenario, + runHttp2Scenario, + runHttpScenario, + runKafkaScenario, + runMongoScenario, + runMySqlScenario, + runNamedScenario, + runPostgresScenario, + runRedisScenario, + runSqsScenario +} from '../lib/dependencies'; +import { error, info } from '../lib/log'; +import { loadMatrixProto } from '../lib/proto'; + +const config = loadConfig(); +const app = express(); + +app.use(express.json()); + +interface DedupOrderLineItem { + quantity: number; + sku: string; +} + +interface DedupOrderPayload { + customerTier: string; + items: DedupOrderLineItem[]; + orderId: string; +} + +function logScenarioStart(name: string): void { + info('scenario started', { scenario: name }); +} + +function logScenarioSuccess(name: string, payload: unknown): void { + info('scenario completed', { scenario: name, payload }); +} + +function logScenarioFailure(name: string, err: unknown): void { + error('scenario failed', { + scenario: name, + error: err instanceof Error ? err.message : String(err) + }); +} + +async function executeScenario(res: Response, scenarioName: string, runner: () => Promise): Promise { + logScenarioStart(scenarioName); + + try { + const result = await runner(); + logScenarioSuccess(scenarioName, { status: 'ok' }); + res.status(200).json(result); + } catch (err) { + logScenarioFailure(scenarioName, err); + res.status(500).json({ + scenario: scenarioName, + status: 'error', + error: err instanceof Error ? err.message : String(err) + }); + } +} + +function stableSerialize(value: unknown): string { + if (Array.isArray(value)) { + return `[${value.map((item) => stableSerialize(item)).join(',')}]`; + } + + if (value && typeof value === 'object') { + const record = value as Record; + return `{${Object.keys(record) + .sort() + .map((key) => `${JSON.stringify(key)}:${stableSerialize(record[key])}`) + .join(',')}}`; + } + + return JSON.stringify(value) ?? 'null'; +} + +function normalizeDedupOrder(body: unknown): DedupOrderPayload | null { + if (!body || typeof body !== 'object') { + return null; + } + + const source = body as Record; + const orderId = typeof source.orderId === 'string' ? source.orderId.trim() : ''; + if (!orderId) { + return null; + } + + const customerTier = typeof source.customerTier === 'string' && source.customerTier.trim() ? source.customerTier.trim() : 'standard'; + const rawItems = Array.isArray(source.items) ? source.items : []; + + const items = rawItems + .map((item) => { + if (!item || typeof item !== 'object') { + return null; + } + + const record = item as Record; + const sku = typeof record.sku === 'string' ? record.sku.trim() : ''; + const quantity = typeof record.quantity === 'number' ? record.quantity : Number.parseInt(String(record.quantity ?? ''), 10); + if (!sku || !Number.isInteger(quantity) || quantity <= 0) { + return null; + } + + return { + sku, + quantity + }; + }) + .filter((item): item is DedupOrderLineItem => item !== null) + .sort((left, right) => left.sku.localeCompare(right.sku) || left.quantity - right.quantity); + + if (items.length === 0) { + return null; + } + + return { + orderId, + customerTier, + items + }; +} + +function parseTimeoutMs(raw: unknown, fallback: number, max: number): number { + if (typeof raw !== 'string') { + return fallback; + } + + const parsed = Number.parseInt(raw, 10); + if (Number.isNaN(parsed) || parsed <= 0) { + return fallback; + } + + return Math.min(parsed, max); +} + +app.get('/health', (_req, res) => { + res.json({ + status: 'ok', + sampleId: config.sampleId + }); +}); + +app.get('/expectations', (_req, res) => { + res.type('application/json').send(readExpectationsJson(config)); +}); + +app.get('/deps/http', async (_req, res) => executeScenario(res, 'deps-http', () => runHttpScenario(config))); +app.get('/deps/http2', async (_req, res) => executeScenario(res, 'deps-http2', () => runHttp2Scenario(config))); +app.get('/deps/grpc', async (_req, res) => executeScenario(res, 'deps-grpc', () => runGrpcScenario(config))); +app.get('/deps/mysql', async (_req, res) => executeScenario(res, 'deps-mysql', () => runMySqlScenario(config))); +app.get('/deps/postgres', async (_req, res) => executeScenario(res, 'deps-postgres', () => runPostgresScenario(config))); +app.get('/deps/mongo', async (_req, res) => executeScenario(res, 'deps-mongo', () => runMongoScenario(config))); +app.get('/deps/redis', async (_req, res) => executeScenario(res, 'deps-redis', () => runRedisScenario(config))); +app.get('/deps/kafka', async (_req, res) => executeScenario(res, 'deps-kafka', () => runKafkaScenario(config))); +app.get('/deps/sqs', async (_req, res) => executeScenario(res, 'deps-sqs', () => runSqsScenario(config))); +app.get('/deps/generic', async (_req, res) => executeScenario(res, 'deps-generic', () => runGenericScenario(config))); +app.get('/deps/all', async (_req, res) => executeScenario(res, 'deps-all', () => runAllDependencyScenarios(config))); + +app.get('/dedup/catalog', (req, res) => { + const itemId = typeof req.query.id === 'string' ? req.query.id : 'alpha'; + logScenarioStart('dedup/catalog'); + + const payload = { + scenario: 'dedup/catalog', + itemId, + description: `catalog-${itemId}`, + stablePrice: 42 + }; + + logScenarioSuccess('dedup/catalog', payload); + res.json(payload); +}); + +app.post('/dedup/order', (req, res) => { + const normalizedOrder = normalizeDedupOrder(req.body); + if (!normalizedOrder) { + return res.status(400).json({ + scenario: 'dedup/order', + error: 'orderId and at least one valid item are required' + }); + } + + const dedupKey = req.get('x-matrix-dedup-key')?.trim() || normalizedOrder.orderId; + logScenarioStart('dedup/order'); + + const payload = { + scenario: 'dedup/order', + dedupKey, + order: normalizedOrder, + stableFingerprint: stableSerialize({ + dedupKey, + order: normalizedOrder + }) + }; + + logScenarioSuccess('dedup/order', payload); + return res.json(payload); +}); + +app.post('/async/catalog-sync', (req, res) => { + const jobId = typeof req.body?.jobId === 'string' ? req.body.jobId.trim() : ''; + const catalogId = typeof req.body?.catalogId === 'string' && req.body.catalogId.trim() ? req.body.catalogId.trim() : 'alpha'; + const correlationId = req.get('x-matrix-correlation-id')?.trim() || jobId; + + if (!jobId) { + return res.status(400).json({ + scenario: 'async/catalog-sync', + error: 'jobId is required' + }); + } + + logScenarioStart('async/catalog-sync'); + const payload = startCatalogSyncJob(config, { + jobId, + catalogId, + correlationId + }); + logScenarioSuccess('async/catalog-sync', payload); + return res.status(202).json(payload); +}); + +app.get('/async/catalog-sync/:jobId', (req, res) => { + const payload = getCatalogSyncJob(req.params.jobId); + if (!payload) { + return res.status(404).json({ + scenario: 'async/catalog-sync', + error: 'job not found', + jobId: req.params.jobId + }); + } + + return res.json(payload); +}); + +app.get('/async/catalog-sync/:jobId/wait', async (req, res) => { + const timeoutMs = parseTimeoutMs(req.query.timeoutMs, 5000, 15000); + logScenarioStart('async/catalog-sync/wait'); + + const payload = await waitForCatalogSyncJob(req.params.jobId, timeoutMs); + if (!payload) { + logScenarioFailure('async/catalog-sync/wait', 'job not found'); + return res.status(404).json({ + scenario: 'async/catalog-sync/wait', + error: 'job not found', + jobId: req.params.jobId + }); + } + + if (payload.status === 'completed') { + logScenarioSuccess('async/catalog-sync/wait', { + jobId: payload.jobId, + status: payload.status, + completedScenarios: payload.completedScenarios + }); + return res.json(payload); + } + + if (payload.status === 'failed') { + logScenarioFailure('async/catalog-sync/wait', payload.error ?? 'async job failed'); + return res.status(500).json(payload); + } + + logScenarioFailure('async/catalog-sync/wait', `timed out after ${timeoutMs}ms`); + return res.status(504).json({ + ...payload, + scenario: 'async/catalog-sync/wait', + error: `timed out after ${timeoutMs}ms` + }); +}); + +app.get('/noise/runtime', (_req, res) => { + const payload = { + scenario: 'noise/runtime', + runtime: { + requestId: crypto.randomUUID(), + servedAt: new Date().toISOString() + } + }; + + res.setHeader('x-matrix-generated-at', new Date().toISOString()); + res.setHeader('etag', `"${crypto.randomUUID()}"`); + logScenarioSuccess('noise/runtime', payload); + res.json(payload); +}); + +app.get('/expected-fail/time-window', (req, res) => { + const ts = typeof req.query.ts === 'string' ? Number.parseInt(req.query.ts, 10) : Number.NaN; + const now = Math.floor(Date.now() / 1000); + + if (Number.isNaN(ts)) { + return res.status(400).json({ + scenario: 'expected-fail/time-window', + error: 'ts query parameter is required' + }); + } + + const diff = Math.abs(now - ts); + if (diff > 2) { + return res.status(400).json({ + scenario: 'expected-fail/time-window', + diff, + now + }); + } + + return res.status(200).json({ + scenario: 'expected-fail/time-window', + diff, + now + }); +}); + +const httpServer = app.listen(config.port, () => { + info('http server listening', { + port: config.port, + sampleId: config.sampleId + }); +}); + +const proto = loadMatrixProto(config.protoPath); +const grpcServer = new grpc.Server(); + +grpcServer.addService(proto.dependencymatrix.DependencyMatrix.service, { + Ping( + call: grpc.ServerUnaryCall<{ name: string }, { message: string; sampleId: string }>, + callback: grpc.sendUnaryData<{ message: string; sampleId: string }> + ) { + callback(null, { + message: `pong:${call.request.name || 'matrix'}`, + sampleId: config.sampleId + }); + }, + + async RunDependencyScenario( + call: grpc.ServerUnaryCall<{ scenario: string }, { scenario: string; status: string; payloadJson: string }>, + callback: grpc.sendUnaryData<{ scenario: string; status: string; payloadJson: string }> + ) { + try { + const result = await runNamedScenario(config, call.request.scenario); + callback(null, { + scenario: result.scenario, + status: 'ok', + payloadJson: JSON.stringify(result.payload) + }); + } catch (err) { + callback({ + code: grpc.status.INTERNAL, + message: err instanceof Error ? err.message : String(err), + name: 'RunDependencyScenarioError' + }); + } + } +}); + +grpcServer.bindAsync(`0.0.0.0:${config.grpcPort}`, grpc.ServerCredentials.createInsecure(), (err) => { + if (err) { + error('grpc server failed to bind', { error: err.message }); + process.exitCode = 1; + return; + } + + grpcServer.start(); + info('grpc server listening', { + port: config.grpcPort + }); +}); + +const shutdown = (): void => { + info('shutdown started'); + grpcServer.tryShutdown(() => { + httpServer.close(() => { + info('shutdown complete'); + process.exit(0); + }); + }); +}; + +process.on('SIGINT', shutdown); +process.on('SIGTERM', shutdown); diff --git a/node-dependency-matrix/src/bin/dependencyFixture.ts b/node-dependency-matrix/src/bin/dependencyFixture.ts new file mode 100644 index 0000000..cee8096 --- /dev/null +++ b/node-dependency-matrix/src/bin/dependencyFixture.ts @@ -0,0 +1,106 @@ +import fs from 'node:fs'; +import http2 from 'node:http2'; +import path from 'node:path'; +import tls from 'node:tls'; + +import * as grpc from '@grpc/grpc-js'; +import express from 'express'; + +import { loadConfig } from '../lib/config'; +import { info } from '../lib/log'; +import { loadMatrixProto } from '../lib/proto'; + +const config = loadConfig(); +const cert = fs.readFileSync(config.proxyCertPath); +const key = fs.readFileSync(config.proxyKeyPath); + +const app = express(); +app.get('/http-json', (req, res) => { + res.json({ + source: 'fixture-service', + protocol: 'http', + echo: req.query.source ?? 'unknown' + }); +}); + +const httpsServer = tls.createServer({ cert, key }, (socket) => { + socket.once('data', (data) => { + const input = data.toString('utf8'); + if (input.startsWith('GET ') || input.startsWith('POST ')) { + socket.destroy(); + } + }); +}); +httpsServer.close(); + +const httpsApp = app.listen(0); +httpsApp.close(); + +require('node:https') + .createServer({ cert, key }, app) + .listen(8443, () => { + info('fixture https server listening', { port: 8443 }); + }); + +http2 + .createSecureServer({ cert, key }) + .on('stream', (stream, headers) => { + if (headers[':path'] === '/http2-ping') { + stream.respond({ + ':status': 200, + 'content-type': 'application/json' + }); + stream.end( + JSON.stringify({ + source: 'fixture-service', + protocol: 'http2', + path: '/http2-ping' + }) + ); + return; + } + + stream.respond({ ':status': 404 }); + stream.end(); + }) + .listen(9443, () => { + info('fixture http2 server listening', { port: 9443 }); + }); + +tls + .createServer({ cert, key }, (socket) => { + socket.on('data', (data) => { + const response = `fixture-ack:${data.toString('utf8').trim()}\n`; + socket.write(response); + }); + }) + .listen(9445, () => { + info('fixture generic tls server listening', { port: 9445 }); + }); + +const proto = loadMatrixProto(config.protoPath); +const grpcServer = new grpc.Server(); +grpcServer.addService(proto.dependencymatrix.DependencyFixture.service, { + GetDependencyQuote( + call: grpc.ServerUnaryCall<{ scenario: string }, { scenario: string; message: string; unixTime: number }>, + callback: grpc.sendUnaryData<{ scenario: string; message: string; unixTime: number }> + ) { + callback(null, { + scenario: call.request.scenario || 'unknown', + message: 'fixture-service-ready', + unixTime: Math.floor(Date.now() / 1000) + }); + } +}); + +grpcServer.bindAsync( + '0.0.0.0:50051', + grpc.ServerCredentials.createSsl(null, [{ cert_chain: cert, private_key: key }]), + (err) => { + if (err) { + throw err; + } + grpcServer.start(); + info('fixture grpc server listening', { port: 50051 }); + } +); diff --git a/node-dependency-matrix/src/bin/tlsProxy.ts b/node-dependency-matrix/src/bin/tlsProxy.ts new file mode 100644 index 0000000..faea562 --- /dev/null +++ b/node-dependency-matrix/src/bin/tlsProxy.ts @@ -0,0 +1,52 @@ +import fs from 'node:fs'; +import net from 'node:net'; +import tls from 'node:tls'; + +import { loadConfig } from '../lib/config'; +import { error, info } from '../lib/log'; + +const config = loadConfig(); +const listenPort = Number.parseInt(process.env.LISTEN_PORT ?? '0', 10); +const targetHost = process.env.TARGET_HOST; +const targetPort = Number.parseInt(process.env.TARGET_PORT ?? '0', 10); + +if (!listenPort || !targetHost || !targetPort) { + throw new Error('LISTEN_PORT, TARGET_HOST and TARGET_PORT are required'); +} + +const cert = fs.readFileSync(config.proxyCertPath); +const key = fs.readFileSync(config.proxyKeyPath); + +const server = tls.createServer({ cert, key }, (sourceSocket) => { + const targetSocket = net.connect(targetPort, targetHost, () => { + sourceSocket.pipe(targetSocket); + targetSocket.pipe(sourceSocket); + }); + + sourceSocket.on('error', (err) => { + error('tls proxy source socket error', { + listenPort, + targetHost, + targetPort, + error: err.message + }); + }); + + targetSocket.on('error', (err) => { + error('tls proxy target socket error', { + listenPort, + targetHost, + targetPort, + error: err.message + }); + sourceSocket.destroy(err); + }); +}); + +server.listen(listenPort, '0.0.0.0', () => { + info('tls proxy listening', { + listenPort, + targetHost, + targetPort + }); +}); diff --git a/node-dependency-matrix/src/global.d.ts b/node-dependency-matrix/src/global.d.ts new file mode 100644 index 0000000..cb2d573 --- /dev/null +++ b/node-dependency-matrix/src/global.d.ts @@ -0,0 +1,3 @@ +declare const crypto: { + randomUUID(): string; +}; diff --git a/node-dependency-matrix/src/lib/asyncJobs.ts b/node-dependency-matrix/src/lib/asyncJobs.ts new file mode 100644 index 0000000..e232131 --- /dev/null +++ b/node-dependency-matrix/src/lib/asyncJobs.ts @@ -0,0 +1,129 @@ +import { AppConfig } from './config'; +import { runHttpScenario, runRedisScenario, runSqsScenario } from './dependencies'; +import { error, info } from './log'; + +type CatalogSyncStatus = 'queued' | 'running' | 'completed' | 'failed'; + +interface CatalogSyncJobState { + jobId: string; + catalogId: string; + correlationId: string; + status: CatalogSyncStatus; + plannedScenarios: string[]; + completedScenarios: string[]; + protocols: string[]; + error?: string; +} + +export interface CatalogSyncJobSnapshot { + scenario: 'async/catalog-sync'; + jobId: string; + catalogId: string; + correlationId: string; + status: CatalogSyncStatus; + plannedScenarios: string[]; + completedScenarios: string[]; + protocols: string[]; + error?: string; +} + +interface CatalogSyncRequest { + jobId: string; + catalogId: string; + correlationId: string; +} + +const catalogSyncRunners = [ + ['deps-http', runHttpScenario], + ['deps-redis', runRedisScenario], + ['deps-sqs', runSqsScenario] +] as const; + +const catalogSyncJobs = new Map(); + +function snapshotJob(state: CatalogSyncJobState): CatalogSyncJobSnapshot { + return { + scenario: 'async/catalog-sync', + jobId: state.jobId, + catalogId: state.catalogId, + correlationId: state.correlationId, + status: state.status, + plannedScenarios: [...state.plannedScenarios], + completedScenarios: [...state.completedScenarios], + protocols: [...state.protocols], + ...(state.error ? { error: state.error } : {}) + }; +} + +async function runCatalogSyncJob(config: AppConfig, state: CatalogSyncJobState): Promise { + state.status = 'running'; + info('async catalog sync started', { + scenario: 'async/catalog-sync', + jobId: state.jobId, + catalogId: state.catalogId, + correlationId: state.correlationId + }); + + try { + for (const [_expectedScenario, runner] of catalogSyncRunners) { + const result = await runner(config); + state.completedScenarios.push(result.scenario); + state.protocols.push(result.protocol); + } + + state.status = 'completed'; + info('async catalog sync completed', { ...snapshotJob(state) }); + } catch (err) { + state.status = 'failed'; + state.error = err instanceof Error ? err.message : String(err); + error('async catalog sync failed', { + scenario: 'async/catalog-sync', + jobId: state.jobId, + catalogId: state.catalogId, + error: state.error + }); + } +} + +export function startCatalogSyncJob(config: AppConfig, request: CatalogSyncRequest): CatalogSyncJobSnapshot { + const state: CatalogSyncJobState = { + jobId: request.jobId, + catalogId: request.catalogId, + correlationId: request.correlationId, + status: 'queued', + plannedScenarios: catalogSyncRunners.map(([scenario]) => scenario), + completedScenarios: [], + protocols: [] + }; + + catalogSyncJobs.set(state.jobId, state); + setImmediate(() => { + void runCatalogSyncJob(config, state); + }); + + return snapshotJob(state); +} + +export function getCatalogSyncJob(jobId: string): CatalogSyncJobSnapshot | null { + const job = catalogSyncJobs.get(jobId); + return job ? snapshotJob(job) : null; +} + +export async function waitForCatalogSyncJob(jobId: string, timeoutMs: number): Promise { + const deadline = Date.now() + timeoutMs; + + while (Date.now() <= deadline) { + const snapshot = getCatalogSyncJob(jobId); + if (!snapshot) { + return null; + } + + if (snapshot.status === 'completed' || snapshot.status === 'failed') { + return snapshot; + } + + await new Promise((resolve) => setTimeout(resolve, 50)); + } + + return getCatalogSyncJob(jobId); +} diff --git a/node-dependency-matrix/src/lib/config.ts b/node-dependency-matrix/src/lib/config.ts new file mode 100644 index 0000000..98b1ad2 --- /dev/null +++ b/node-dependency-matrix/src/lib/config.ts @@ -0,0 +1,82 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +export interface AppConfig { + port: number; + grpcPort: number; + sampleId: string; + fixtureHttpsBase: string; + fixtureHttp2Origin: string; + fixtureGrpcTarget: string; + fixtureGenericHost: string; + fixtureGenericPort: number; + mysqlUrl: string; + postgresUrl: string; + mongoUrl: string; + redisUrl: string; + kafkaBrokers: string[]; + kafkaTopic: string; + sqsEndpoint: string; + sqsQueueUrl: string; + sqsRegion: string; + caBundlePath: string; + proxyCertPath: string; + proxyKeyPath: string; + expectationsPath: string; + protoPath: string; +} + +function readNumber(name: string, fallback: number): number { + const raw = process.env[name]; + if (!raw) { + return fallback; + } + + const parsed = Number.parseInt(raw, 10); + return Number.isNaN(parsed) ? fallback : parsed; +} + +function splitCsv(raw: string | undefined, fallback: string[]): string[] { + if (!raw) { + return fallback; + } + return raw + .split(',') + .map((item) => item.trim()) + .filter(Boolean); +} + +export function loadConfig(): AppConfig { + const workdir = process.cwd(); + const expectationsPath = path.join(workdir, 'fixtures', 'expected-values.json'); + const protoPath = path.join(workdir, 'proto', 'dependency_matrix.proto'); + + return { + port: readNumber('PORT', 8080), + grpcPort: readNumber('GRPC_PORT', 9090), + sampleId: process.env.SAMPLE_ID ?? 'node-dependency-matrix', + fixtureHttpsBase: process.env.FIXTURE_HTTPS_BASE ?? 'https://fixture-service:8443', + fixtureHttp2Origin: process.env.FIXTURE_HTTP2_ORIGIN ?? 'https://fixture-service:9443', + fixtureGrpcTarget: process.env.FIXTURE_GRPC_TARGET ?? 'fixture-service:50051', + fixtureGenericHost: process.env.FIXTURE_GENERIC_HOST ?? 'fixture-service', + fixtureGenericPort: readNumber('FIXTURE_GENERIC_PORT', 9445), + mysqlUrl: process.env.MYSQL_URL ?? 'mysql://root@mysql:3306/matrix', + postgresUrl: process.env.POSTGRES_URL ?? 'postgresql://postgres@postgres:5432/matrix', + mongoUrl: process.env.MONGO_URL ?? 'mongodb://mongo-tls:27017/matrix?tls=true', + redisUrl: process.env.REDIS_URL ?? 'rediss://redis-tls:6380', + kafkaBrokers: splitCsv(process.env.KAFKA_BROKERS, ['redpanda:9092']), + kafkaTopic: process.env.KAFKA_TOPIC ?? 'matrix-events', + sqsEndpoint: process.env.SQS_ENDPOINT ?? 'https://sqs-tls:4567', + sqsQueueUrl: process.env.SQS_QUEUE_URL ?? 'https://sqs-tls:4567/000000000000/dependency-matrix', + sqsRegion: process.env.SQS_REGION ?? 'us-east-1', + caBundlePath: process.env.COMBINED_CA_CERT_PATH ?? process.env.NODE_EXTRA_CA_CERTS ?? '/tmp/node-dependency-matrix-ca-bundle.crt', + proxyCertPath: process.env.TLS_CERT_PATH ?? '/etc/sample-certs/proxy.crt', + proxyKeyPath: process.env.TLS_KEY_PATH ?? '/etc/sample-certs/proxy.key', + expectationsPath, + protoPath + }; +} + +export function readExpectationsJson(config: AppConfig): string { + return fs.readFileSync(config.expectationsPath, 'utf8'); +} diff --git a/node-dependency-matrix/src/lib/dependencies.ts b/node-dependency-matrix/src/lib/dependencies.ts new file mode 100644 index 0000000..69b07fa --- /dev/null +++ b/node-dependency-matrix/src/lib/dependencies.ts @@ -0,0 +1,493 @@ +import fs from 'node:fs'; +import http2 from 'node:http2'; +import https from 'node:https'; +import tls from 'node:tls'; +import { promisify } from 'node:util'; +import { URL } from 'node:url'; + +import { DeleteMessageCommand, ReceiveMessageCommand, SendMessageCommand, SQSClient } from '@aws-sdk/client-sqs'; +import * as grpc from '@grpc/grpc-js'; +import { Kafka } from 'kafkajs'; +import Redis from 'ioredis'; +import { MongoClient } from 'mongodb'; +import mysql from 'mysql2/promise'; +import { Client as PgClient } from 'pg'; +import { NodeHttpHandler } from '@smithy/node-http-handler'; + +import { AppConfig } from './config'; +import { error, info } from './log'; +import { FixtureGrpcClient, loadMatrixProto } from './proto'; + +type ScenarioPayload = Record; + +export interface ScenarioResult { + scenario: string; + protocol: string; + payload: ScenarioPayload; +} + +let cachedCaBundle: Buffer | undefined; +function readCaBundle(config: AppConfig): Buffer { + if (!cachedCaBundle) { + cachedCaBundle = fs.readFileSync(config.caBundlePath); + } + return cachedCaBundle; +} + +export async function runHttpScenario(config: AppConfig): Promise { + const target = new URL(`${config.fixtureHttpsBase}/http-json?source=app`); + const payload = await new Promise<{ status: number; body: ScenarioPayload }>((resolve, reject) => { + const request = https.request( + { + protocol: target.protocol, + hostname: target.hostname, + port: target.port, + path: `${target.pathname}${target.search}`, + method: 'GET', + ca: readCaBundle(config), + rejectUnauthorized: true + }, + (response) => { + const chunks: Buffer[] = []; + response.on('data', (chunk) => chunks.push(Buffer.from(chunk))); + response.on('end', () => { + const body = Buffer.concat(chunks).toString('utf8'); + resolve({ + status: response.statusCode ?? 0, + body: JSON.parse(body) as ScenarioPayload + }); + }); + } + ); + + request.on('error', reject); + request.end(); + }); + + return { + scenario: 'deps-http', + protocol: 'Http', + payload: { + status: payload.status, + body: payload.body + } + }; +} + +export async function runHttp2Scenario(config: AppConfig): Promise { + const client = http2.connect(config.fixtureHttp2Origin, { + ca: readCaBundle(config) + }); + + try { + const payload = await new Promise((resolve, reject) => { + const request = client.request({ + ':method': 'GET', + ':path': '/http2-ping' + }); + + const chunks: Buffer[] = []; + + request.on('response', () => undefined); + request.on('data', (chunk) => chunks.push(Buffer.from(chunk))); + request.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); + request.on('error', reject); + request.end(); + }); + + return { + scenario: 'deps-http2', + protocol: 'Http2', + payload: JSON.parse(payload) as ScenarioPayload + }; + } finally { + client.close(); + } +} + +export async function runGrpcScenario(config: AppConfig): Promise { + const proto = loadMatrixProto(config.protoPath); + const ClientCtor = proto.dependencymatrix.DependencyFixture; + const client = new ClientCtor( + config.fixtureGrpcTarget, + grpc.credentials.createSsl(readCaBundle(config)) + ) as unknown as FixtureGrpcClient; + const getQuote = promisify<{ scenario: string }, { scenario: string; message: string; unixTime: string | number }>( + client.GetDependencyQuote.bind(client) + ); + + try { + const response = await getQuote({ scenario: 'deps-grpc' }); + + return { + scenario: 'deps-grpc', + protocol: 'gRPC', + payload: { + scenario: response.scenario, + message: response.message, + unixTime: Number(response.unixTime) + } + }; + } finally { + client.close(); + } +} + +export async function runMySqlScenario(config: AppConfig): Promise { + const connection = await mysql.createConnection({ + uri: config.mysqlUrl + }); + + try { + await connection.execute(` + CREATE TABLE IF NOT EXISTS matrix_events ( + sample_key VARCHAR(64) PRIMARY KEY, + sample_value VARCHAR(128) NOT NULL, + touched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) + `); + + await connection.execute( + ` + INSERT INTO matrix_events (sample_key, sample_value) + VALUES (?, ?) + ON DUPLICATE KEY UPDATE sample_value = VALUES(sample_value) + `, + ['mysql-scenario', 'mysql-value'] + ); + + const [rows] = await connection.query( + 'SELECT sample_key, sample_value FROM matrix_events WHERE sample_key = ?', + ['mysql-scenario'] + ); + + return { + scenario: 'deps-mysql', + protocol: 'MySQL', + payload: { + rowCount: rows.length, + row: rows[0] ?? null + } + }; + } finally { + await connection.end(); + } +} + +export async function runPostgresScenario(config: AppConfig): Promise { + const client = new PgClient({ + connectionString: config.postgresUrl + }); + + await client.connect(); + + try { + await client.query(` + CREATE TABLE IF NOT EXISTS matrix_events ( + sample_key TEXT PRIMARY KEY, + sample_value TEXT NOT NULL + ) + `); + + await client.query( + ` + INSERT INTO matrix_events (sample_key, sample_value) + VALUES ($1, $2) + ON CONFLICT (sample_key) DO UPDATE SET sample_value = EXCLUDED.sample_value + `, + ['postgres-scenario', 'postgres-value'] + ); + + const result = await client.query( + 'SELECT sample_key, sample_value FROM matrix_events WHERE sample_key = $1', + ['postgres-scenario'] + ); + + return { + scenario: 'deps-postgres', + protocol: 'PostgresV2', + payload: { + rowCount: result.rowCount, + row: result.rows[0] ?? null + } + }; + } finally { + await client.end(); + } +} + +export async function runMongoScenario(config: AppConfig): Promise { + const client = new MongoClient(config.mongoUrl, { + tlsCAFile: config.caBundlePath + }); + await client.connect(); + + try { + const database = client.db('matrix'); + const collection = database.collection('events'); + + await collection.updateOne( + { sampleKey: 'mongo-scenario' }, + { + $set: { + sampleValue: 'mongo-value', + updatedAt: new Date().toISOString() + } + }, + { upsert: true } + ); + + const document = await collection.findOne({ sampleKey: 'mongo-scenario' }, { projection: { _id: 0 } }); + + return { + scenario: 'deps-mongo', + protocol: 'Mongo', + payload: document ?? {} + }; + } finally { + await client.close(); + } +} + +export async function runRedisScenario(config: AppConfig): Promise { + const client = new Redis(config.redisUrl, { + tls: { + ca: readCaBundle(config), + rejectUnauthorized: true, + checkServerIdentity: () => undefined + }, + lazyConnect: true, + maxRetriesPerRequest: 1 + }); + client.on('error', () => undefined); + + await client.connect(); + + try { + await client.set('matrix:redis-scenario', 'redis-value'); + const value = await client.get('matrix:redis-scenario'); + + return { + scenario: 'deps-redis', + protocol: 'Redis', + payload: { + value + } + }; + } finally { + client.disconnect(); + } +} + +export async function runKafkaScenario(config: AppConfig): Promise { + const kafka = new Kafka({ + clientId: config.sampleId, + brokers: config.kafkaBrokers, + ssl: false, + retry: { + retries: 0 + } + }); + + const admin = kafka.admin(); + + await admin.connect(); + + try { + const cluster = await admin.describeCluster(); + const topics = await admin.listTopics(); + + return { + scenario: 'deps-kafka', + protocol: 'Kafka', + payload: { + brokerCount: cluster.brokers.length, + controller: cluster.controller ?? null, + topicCount: topics.length, + hasMatrixTopic: topics.includes(config.kafkaTopic) + } + }; + } finally { + await admin.disconnect().catch(() => undefined); + } +} + +export async function runSqsScenario(config: AppConfig): Promise { + const client = new SQSClient({ + endpoint: config.sqsEndpoint, + region: config.sqsRegion, + md5: false, + requestHandler: new NodeHttpHandler({ + httpsAgent: new https.Agent({ + ca: readCaBundle(config), + rejectUnauthorized: true + }) + }), + credentials: { + accessKeyId: 'test', + secretAccessKey: 'test' + } + }); + + const sendResult = await client.send( + new SendMessageCommand({ + QueueUrl: config.sqsQueueUrl, + MessageBody: JSON.stringify({ + sampleId: config.sampleId, + scenario: 'deps-sqs', + sentAt: new Date().toISOString() + }) + }) + ); + + const receiveResult = await client.send( + new ReceiveMessageCommand({ + QueueUrl: config.sqsQueueUrl, + MaxNumberOfMessages: 1, + WaitTimeSeconds: 0 + }) + ); + + const receiptHandle = receiveResult.Messages?.[0]?.ReceiptHandle; + if (receiptHandle) { + await client.send( + new DeleteMessageCommand({ + QueueUrl: config.sqsQueueUrl, + ReceiptHandle: receiptHandle + }) + ); + } + + return { + scenario: 'deps-sqs', + protocol: 'SQS', + payload: { + sentMessageId: sendResult.MessageId ?? null, + receivedMessages: receiveResult.Messages?.length ?? 0 + } + }; +} + +export async function runGenericScenario(config: AppConfig): Promise { + const payload = await new Promise((resolve, reject) => { + let settled = false; + const socket = tls.connect( + { + host: config.fixtureGenericHost, + port: config.fixtureGenericPort, + ca: readCaBundle(config), + rejectUnauthorized: true, + checkServerIdentity: () => undefined + }, + () => { + socket.end('matrix-generic\n'); + } + ); + + const fail = (err: Error) => { + if (settled) { + return; + } + + settled = true; + socket.destroy(); + reject(err); + }; + + socket.setTimeout(3000, () => { + fail(new Error('generic socket timeout')); + }); + + socket.once('data', (data) => { + if (settled) { + return; + } + + settled = true; + resolve(data.toString('utf8').trim()); + socket.destroy(); + }); + + socket.once('end', () => { + fail(new Error('generic socket closed before response')); + }); + + socket.once('error', (err) => { + fail(err); + }); + }); + + return { + scenario: 'deps-generic', + protocol: 'Generic', + payload: { + echoed: payload + } + }; +} + +export async function runNamedScenario(config: AppConfig, scenario: string): Promise { + switch (scenario) { + case 'http': + case 'deps-http': + return runHttpScenario(config); + case 'http2': + case 'deps-http2': + return runHttp2Scenario(config); + case 'grpc': + case 'deps-grpc': + return runGrpcScenario(config); + case 'mysql': + case 'deps-mysql': + return runMySqlScenario(config); + case 'postgres': + case 'deps-postgres': + return runPostgresScenario(config); + case 'mongo': + case 'deps-mongo': + return runMongoScenario(config); + case 'redis': + case 'deps-redis': + return runRedisScenario(config); + case 'kafka': + case 'deps-kafka': + return runKafkaScenario(config); + case 'sqs': + case 'deps-sqs': + return runSqsScenario(config); + case 'generic': + case 'deps-generic': + return runGenericScenario(config); + default: + throw new Error(`unsupported scenario: ${scenario}`); + } +} + +export async function runAllDependencyScenarios(config: AppConfig): Promise { + const runners = [ + runHttpScenario, + runHttp2Scenario, + runGrpcScenario, + runMySqlScenario, + runPostgresScenario, + runMongoScenario, + runRedisScenario, + runKafkaScenario, + runSqsScenario, + runGenericScenario + ]; + + const results: ScenarioResult[] = []; + for (const runner of runners) { + try { + results.push(await runner(config)); + } catch (err) { + error('dependency scenario failed', { + scenario: runner.name, + error: err instanceof Error ? err.message : String(err) + }); + throw err; + } + } + return results; +} diff --git a/node-dependency-matrix/src/lib/log.ts b/node-dependency-matrix/src/lib/log.ts new file mode 100644 index 0000000..3bcc056 --- /dev/null +++ b/node-dependency-matrix/src/lib/log.ts @@ -0,0 +1,21 @@ +type LogLevel = 'INFO' | 'ERROR'; + +function log(level: LogLevel, message: string, details: Record = {}): void { + const payload = { + level, + message, + ts: new Date().toISOString(), + ...details + }; + + const output = level === 'ERROR' ? console.error : console.log; + output(JSON.stringify(payload)); +} + +export function info(message: string, details: Record = {}): void { + log('INFO', message, details); +} + +export function error(message: string, details: Record = {}): void { + log('ERROR', message, details); +} diff --git a/node-dependency-matrix/src/lib/proto.ts b/node-dependency-matrix/src/lib/proto.ts new file mode 100644 index 0000000..222ec28 --- /dev/null +++ b/node-dependency-matrix/src/lib/proto.ts @@ -0,0 +1,44 @@ +import path from 'node:path'; + +import * as grpc from '@grpc/grpc-js'; +import * as protoLoader from '@grpc/proto-loader'; + +const loaderOptions: protoLoader.Options = { + keepCase: false, + longs: String, + enums: String, + defaults: true, + oneofs: true +}; + +export interface FixtureGrpcClient extends grpc.Client { + GetDependencyQuote( + request: { scenario: string }, + callback: (error: grpc.ServiceError | null, response: { scenario: string; message: string; unixTime: string | number }) => void + ): void; +} + +export interface MatrixGrpcClient extends grpc.Client { + Ping( + request: { name: string }, + callback: (error: grpc.ServiceError | null, response: { message: string; sampleId: string }) => void + ): void; + + RunDependencyScenario( + request: { scenario: string }, + callback: (error: grpc.ServiceError | null, response: { scenario: string; status: string; payloadJson: string }) => void + ): void; +} + +export interface LoadedProto { + dependencymatrix: { + DependencyFixture: grpc.ServiceClientConstructor; + DependencyMatrix: grpc.ServiceClientConstructor; + }; +} + +export function loadMatrixProto(protoPath: string): LoadedProto { + const absolutePath = path.resolve(protoPath); + const packageDefinition = protoLoader.loadSync(absolutePath, loaderOptions); + return grpc.loadPackageDefinition(packageDefinition) as unknown as LoadedProto; +} diff --git a/node-dependency-matrix/tsconfig.json b/node-dependency-matrix/tsconfig.json new file mode 100644 index 0000000..97b1ff3 --- /dev/null +++ b/node-dependency-matrix/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "rootDir": "./src", + "outDir": "./dist", + "moduleResolution": "node", + "resolveJsonModule": true, + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"] +}