diff --git a/.env.complete b/.env.complete new file mode 100644 index 0000000..4aea194 --- /dev/null +++ b/.env.complete @@ -0,0 +1,242 @@ +# ======================================================================= +# Stock Bot Platform Environment Configuration +# ======================================================================= + +# Core Application Settings +NODE_ENV=development +PORT=3001 +APP_NAME=stock-bot +APP_VERSION=1.0.0 + +# ======================================================================= +# DATABASE CONFIGURATIONS +# ======================================================================= + +# PostgreSQL - Operational Data (orders, positions, strategies) +POSTGRES_HOST=localhost +POSTGRES_PORT=5432 +POSTGRES_DATABASE=trading_bot +POSTGRES_USERNAME=trading_user +POSTGRES_PASSWORD=trading_pass_dev +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=trading_bot +DB_USER=trading_user +DB_PASSWORD=trading_pass_dev +DB_POOL_MIN=2 +DB_POOL_MAX=10 +DB_POOL_IDLE_TIMEOUT=30000 +DB_SSL=false +DB_SSL_REJECT_UNAUTHORIZED=true +DB_QUERY_TIMEOUT=30000 +DB_CONNECTION_TIMEOUT=5000 + +# QuestDB - Time-series Data (OHLCV, indicators, performance) +QUESTDB_HOST=localhost +QUESTDB_HTTP_PORT=9000 +QUESTDB_PG_PORT=8812 +QUESTDB_INFLUX_PORT=9009 +QUESTDB_USER= +QUESTDB_PASSWORD= +QUESTDB_CONNECTION_TIMEOUT=5000 +QUESTDB_REQUEST_TIMEOUT=30000 +QUESTDB_RETRY_ATTEMPTS=3 +QUESTDB_TLS_ENABLED=false +QUESTDB_DEFAULT_DATABASE=qdb +QUESTDB_TELEMETRY_ENABLED=false + +# MongoDB - Document Storage (sentiment, raw docs, unstructured data) +MONGODB_HOST=localhost +MONGODB_PORT=27017 +MONGODB_DATABASE=trading_documents +MONGODB_USERNAME=trading_admin +MONGODB_PASSWORD=trading_mongo_dev +MONGODB_AUTH_SOURCE=admin +MONGODB_URI= +MONGODB_MAX_POOL_SIZE=10 +MONGODB_MIN_POOL_SIZE=0 +MONGODB_MAX_IDLE_TIME=30000 +MONGODB_CONNECT_TIMEOUT=10000 +MONGODB_SOCKET_TIMEOUT=30000 +MONGODB_SERVER_SELECTION_TIMEOUT=5000 +MONGODB_TLS=false +MONGODB_RETRY_WRITES=true +MONGODB_JOURNAL=true +MONGODB_READ_PREFERENCE=primary +MONGODB_WRITE_CONCERN=majority + +# Dragonfly - Redis Replacement (caching and events) +DRAGONFLY_HOST=localhost +DRAGONFLY_PORT=6379 +DRAGONFLY_PASSWORD= +DRAGONFLY_USERNAME= +DRAGONFLY_DATABASE=0 +DRAGONFLY_MAX_RETRIES=3 +DRAGONFLY_RETRY_DELAY=50 +DRAGONFLY_CONNECT_TIMEOUT=10000 +DRAGONFLY_COMMAND_TIMEOUT=5000 +DRAGONFLY_POOL_SIZE=10 +DRAGONFLY_POOL_MIN=1 +DRAGONFLY_POOL_MAX=20 +DRAGONFLY_TLS=false +DRAGONFLY_ENABLE_KEEPALIVE=true +DRAGONFLY_KEEPALIVE_INTERVAL=60 +DRAGONFLY_CLUSTER_MODE=false +DRAGONFLY_CLUSTER_NODES= +DRAGONFLY_MAX_MEMORY=2gb +DRAGONFLY_CACHE_MODE=true + +# ======================================================================= +# MONITORING & LOGGING CONFIGURATIONS +# ======================================================================= + +# Logging Configuration +LOG_LEVEL=debug +LOG_FORMAT=json +LOG_CONSOLE=true +LOG_FILE=false +LOG_FILE_PATH=logs +LOG_FILE_MAX_SIZE=20m +LOG_FILE_MAX_FILES=14 +LOG_FILE_DATE_PATTERN=YYYY-MM-DD +LOG_ERROR_FILE=true +LOG_ERROR_STACK=true +LOG_PERFORMANCE=false +LOG_SQL_QUERIES=false +LOG_HTTP_REQUESTS=true +LOG_STRUCTURED=true +LOG_TIMESTAMP=true +LOG_CALLER_INFO=false +LOG_SILENT_MODULES= +LOG_VERBOSE_MODULES= +LOG_SERVICE_NAME=stock-bot +LOG_SERVICE_VERSION=1.0.0 +LOG_ENVIRONMENT=development + +# Loki - Log Aggregation +LOKI_HOST=localhost +LOKI_PORT=3100 +LOKI_URL= +LOKI_USERNAME= +LOKI_PASSWORD= +LOKI_TENANT_ID= +LOKI_PUSH_TIMEOUT=10000 +LOKI_BATCH_SIZE=1024 +LOKI_BATCH_WAIT=1000 +LOKI_RETENTION_PERIOD=30d +LOKI_MAX_CHUNK_AGE=1h +LOKI_TLS_ENABLED=false +LOKI_TLS_INSECURE=false +LOKI_DEFAULT_LABELS= +LOKI_SERVICE_LABEL=stock-bot +LOKI_ENVIRONMENT_LABEL=development + +# Prometheus - Metrics Collection +PROMETHEUS_HOST=localhost +PROMETHEUS_PORT=9090 +PROMETHEUS_URL= +PROMETHEUS_USERNAME= +PROMETHEUS_PASSWORD= +PROMETHEUS_SCRAPE_INTERVAL=15s +PROMETHEUS_EVALUATION_INTERVAL=15s +PROMETHEUS_RETENTION_TIME=15d +PROMETHEUS_TLS_ENABLED=false +PROMETHEUS_TLS_INSECURE=false + +# Grafana - Visualization +GRAFANA_HOST=localhost +GRAFANA_PORT=3000 +GRAFANA_URL= +GRAFANA_ADMIN_USER=admin +GRAFANA_ADMIN_PASSWORD=admin +GRAFANA_ALLOW_SIGN_UP=false +GRAFANA_SECRET_KEY= +GRAFANA_DATABASE_TYPE=sqlite3 +GRAFANA_DATABASE_URL= +GRAFANA_DISABLE_GRAVATAR=true +GRAFANA_ENABLE_GZIP=true + +# ======================================================================= +# DATA PROVIDER CONFIGURATIONS +# ======================================================================= + +# Default Data Provider +DEFAULT_DATA_PROVIDER=alpaca + +# Alpaca Markets +ALPACA_ENABLED=true +ALPACA_API_KEY=your_alpaca_key_here +ALPACA_SECRET_KEY=your_alpaca_secret_here +ALPACA_BASE_URL=https://paper-api.alpaca.markets +ALPACA_DATA_URL=https://data.alpaca.markets +ALPACA_PAPER_TRADING=true + +# Polygon.io +POLYGON_ENABLED=false +POLYGON_API_KEY=your_polygon_key_here +POLYGON_BASE_URL=https://api.polygon.io + +# Yahoo Finance +YAHOO_ENABLED=true +YAHOO_BASE_URL=https://query1.finance.yahoo.com + +# IEX Cloud +IEX_ENABLED=false +IEX_API_KEY=your_iex_key_here +IEX_BASE_URL=https://cloud.iexapis.com + +# Alpha Vantage +ALPHA_VANTAGE_ENABLED=false +ALPHA_VANTAGE_API_KEY=demo + +# Data Provider Settings +DATA_PROVIDER_TIMEOUT=30000 +DATA_PROVIDER_RETRIES=3 +DATA_PROVIDER_RETRY_DELAY=1000 +DATA_CACHE_ENABLED=true +DATA_CACHE_TTL=300 +DATA_CACHE_MAX_SIZE=1000 + +# ======================================================================= +# TRADING & RISK MANAGEMENT +# ======================================================================= + +# Trading Configuration +PAPER_TRADING=true +MAX_POSITION_SIZE=0.1 +MAX_DAILY_LOSS=1000 + +# Risk Management +RISK_MAX_POSITION_SIZE=0.25 +RISK_MAX_LEVERAGE=2.0 +RISK_DEFAULT_STOP_LOSS=0.02 +RISK_DEFAULT_TAKE_PROFIT=0.06 +RISK_MAX_DRAWDOWN=0.10 +RISK_MAX_CONSECUTIVE_LOSSES=5 +RISK_POSITION_SIZING_METHOD=fixed_percentage +RISK_CIRCUIT_BREAKER_ENABLED=true +RISK_CIRCUIT_BREAKER_THRESHOLD=0.05 +RISK_CIRCUIT_BREAKER_COOLDOWN=3600000 +RISK_ALLOW_WEEKEND_TRADING=false +RISK_MARKET_HOURS_ONLY=true + +# ======================================================================= +# FEATURE FLAGS +# ======================================================================= +ENABLE_ML_SIGNALS=false +ENABLE_SENTIMENT_ANALYSIS=false +ENABLE_SOCIAL_SIGNALS=false +ENABLE_OPTIONS_TRADING=false +ENABLE_CRYPTO_TRADING=false +ENABLE_BACKTESTING=true +ENABLE_PAPER_TRADING=true +ENABLE_LIVE_TRADING=false + +# ======================================================================= +# DEVELOPMENT & DEBUGGING +# ======================================================================= +DEBUG_MODE=true +VERBOSE_LOGGING=true +MOCK_DATA_PROVIDERS=false +ENABLE_API_RATE_LIMITING=true +ENABLE_REQUEST_LOGGING=true diff --git a/.env.docker b/.env.docker new file mode 100644 index 0000000..4e8dceb --- /dev/null +++ b/.env.docker @@ -0,0 +1,144 @@ +# Docker Environment Configuration +# This file contains environment variables used by Docker Compose + +# ============================================================================= +# CONTAINER NETWORK SETTINGS +# ============================================================================= +COMPOSE_PROJECT_NAME=stock-bot +NETWORK_NAME=trading-bot-network + +# ============================================================================= +# DATABASE CONTAINER SETTINGS +# ============================================================================= + +# PostgreSQL Container +POSTGRES_DB=trading_bot +POSTGRES_USER=trading_user +POSTGRES_PASSWORD=trading_pass_secure +POSTGRES_INITDB_ARGS=--encoding=UTF-8 + +# MongoDB Container +MONGO_INITDB_ROOT_USERNAME=trading_admin +MONGO_INITDB_ROOT_PASSWORD=trading_mongo_secure +MONGO_INITDB_DATABASE=trading_documents + +# QuestDB Container +QDB_TELEMETRY_ENABLED=false + +# Dragonfly Container +DRAGONFLY_MAXMEMORY=4gb +DRAGONFLY_PROACTOR_THREADS=8 + +# ============================================================================= +# MONITORING CONTAINER SETTINGS +# ============================================================================= + +# Grafana Container +GF_SECURITY_ADMIN_USER=admin +GF_SECURITY_ADMIN_PASSWORD=secure_grafana_password +GF_USERS_ALLOW_SIGN_UP=false +GF_PATHS_PROVISIONING=/etc/grafana/provisioning +GF_DISABLE_GRAVATAR=true + +# Prometheus Container +PROMETHEUS_CONFIG_FILE=/etc/prometheus/prometheus.yml +PROMETHEUS_STORAGE_PATH=/prometheus +PROMETHEUS_WEB_ENABLE_LIFECYCLE=true + +# ============================================================================= +# ADMIN INTERFACE CONTAINER SETTINGS +# ============================================================================= + +# PgAdmin Container +PGADMIN_DEFAULT_EMAIL=admin@tradingbot.local +PGADMIN_DEFAULT_PASSWORD=secure_pgadmin_password +PGADMIN_CONFIG_SERVER_MODE=False +PGADMIN_DISABLE_POSTFIX=true + +# Mongo Express Container +ME_CONFIG_MONGODB_ADMINUSERNAME=trading_admin +ME_CONFIG_MONGODB_ADMINPASSWORD=trading_mongo_secure +ME_CONFIG_MONGODB_SERVER=mongodb +ME_CONFIG_MONGODB_PORT=27017 +ME_CONFIG_BASICAUTH_USERNAME=admin +ME_CONFIG_BASICAUTH_PASSWORD=secure_mongo_express_password + +# Redis Insight Container +REDIS_HOSTS=local:dragonfly:6379 + +# ============================================================================= +# VOLUME MOUNT PATHS +# ============================================================================= + +# Data Volume Paths (adjust these for your host system) +POSTGRES_DATA_PATH=./data/postgres +QUESTDB_DATA_PATH=./data/questdb +MONGODB_DATA_PATH=./data/mongodb +DRAGONFLY_DATA_PATH=./data/dragonfly +PROMETHEUS_DATA_PATH=./data/prometheus +GRAFANA_DATA_PATH=./data/grafana +LOKI_DATA_PATH=./data/loki +PGADMIN_DATA_PATH=./data/pgadmin + +# Config Volume Paths +PROMETHEUS_CONFIG_PATH=./monitoring/prometheus +GRAFANA_CONFIG_PATH=./monitoring/grafana +LOKI_CONFIG_PATH=./monitoring/loki + +# Database Init Paths +POSTGRES_INIT_PATH=./database/postgres/init +MONGODB_INIT_PATH=./database/mongodb/init + +# ============================================================================= +# PORT MAPPINGS (HOST:CONTAINER) +# ============================================================================= + +# Database Ports +POSTGRES_PORT=5432 +QUESTDB_HTTP_PORT=9000 +QUESTDB_PG_PORT=8812 +QUESTDB_INFLUX_PORT=9009 +MONGODB_PORT=27017 +DRAGONFLY_PORT=6379 + +# Monitoring Ports +PROMETHEUS_PORT=9090 +GRAFANA_PORT=3000 +LOKI_PORT=3100 + +# Admin Interface Ports +PGADMIN_PORT=8080 +MONGO_EXPRESS_PORT=8081 +REDIS_INSIGHT_PORT=8001 + +# ============================================================================= +# HEALTH CHECK SETTINGS +# ============================================================================= + +# Health Check Intervals +HEALTHCHECK_INTERVAL=30s +HEALTHCHECK_TIMEOUT=10s +HEALTHCHECK_RETRIES=3 +HEALTHCHECK_START_PERIOD=60s + +# ============================================================================= +# RESOURCE LIMITS +# ============================================================================= + +# Memory Limits (uncomment and adjust for production) +# POSTGRES_MEMORY_LIMIT=2g +# QUESTDB_MEMORY_LIMIT=4g +# MONGODB_MEMORY_LIMIT=2g +# DRAGONFLY_MEMORY_LIMIT=4g +# PROMETHEUS_MEMORY_LIMIT=2g +# GRAFANA_MEMORY_LIMIT=512m +# LOKI_MEMORY_LIMIT=1g + +# CPU Limits (uncomment and adjust for production) +# POSTGRES_CPU_LIMIT=1 +# QUESTDB_CPU_LIMIT=2 +# MONGODB_CPU_LIMIT=1 +# DRAGONFLY_CPU_LIMIT=2 +# PROMETHEUS_CPU_LIMIT=1 +# GRAFANA_CPU_LIMIT=0.5 +# LOKI_CPU_LIMIT=1 diff --git a/.env.prod b/.env.prod new file mode 100644 index 0000000..2aa8d1d --- /dev/null +++ b/.env.prod @@ -0,0 +1,233 @@ +# ======================================================================= +# Stock Bot Platform Production Environment Configuration +# ======================================================================= + +# Core Application Settings +NODE_ENV=production +PORT=3001 +APP_NAME=stock-bot +APP_VERSION=1.0.0 + +# ======================================================================= +# DATABASE CONFIGURATIONS +# ======================================================================= + +# PostgreSQL - Operational Data (orders, positions, strategies) +DB_HOST=${DB_HOST} +DB_PORT=${DB_PORT:-5432} +DB_NAME=${DB_NAME} +DB_USER=${DB_USER} +DB_PASSWORD=${DB_PASSWORD} +DB_POOL_MIN=5 +DB_POOL_MAX=20 +DB_POOL_IDLE_TIMEOUT=60000 +DB_SSL=true +DB_SSL_REJECT_UNAUTHORIZED=true +DB_QUERY_TIMEOUT=30000 +DB_CONNECTION_TIMEOUT=10000 + +# QuestDB - Time-series Data (OHLCV, indicators, performance) +QUESTDB_HOST=${QUESTDB_HOST} +QUESTDB_HTTP_PORT=${QUESTDB_HTTP_PORT:-9000} +QUESTDB_PG_PORT=${QUESTDB_PG_PORT:-8812} +QUESTDB_INFLUX_PORT=${QUESTDB_INFLUX_PORT:-9009} +QUESTDB_USER=${QUESTDB_USER} +QUESTDB_PASSWORD=${QUESTDB_PASSWORD} +QUESTDB_CONNECTION_TIMEOUT=10000 +QUESTDB_REQUEST_TIMEOUT=60000 +QUESTDB_RETRY_ATTEMPTS=5 +QUESTDB_TLS_ENABLED=true +QUESTDB_DEFAULT_DATABASE=qdb +QUESTDB_TELEMETRY_ENABLED=false + +# MongoDB - Document Storage (sentiment, raw docs, unstructured data) +MONGODB_HOST=${MONGODB_HOST} +MONGODB_PORT=${MONGODB_PORT:-27017} +MONGODB_DATABASE=${MONGODB_DATABASE} +MONGODB_USERNAME=${MONGODB_USERNAME} +MONGODB_PASSWORD=${MONGODB_PASSWORD} +MONGODB_AUTH_SOURCE=admin +MONGODB_URI=${MONGODB_URI} +MONGODB_MAX_POOL_SIZE=50 +MONGODB_MIN_POOL_SIZE=5 +MONGODB_MAX_IDLE_TIME=60000 +MONGODB_CONNECT_TIMEOUT=30000 +MONGODB_SOCKET_TIMEOUT=60000 +MONGODB_SERVER_SELECTION_TIMEOUT=10000 +MONGODB_TLS=true +MONGODB_RETRY_WRITES=true +MONGODB_JOURNAL=true +MONGODB_READ_PREFERENCE=primaryPreferred +MONGODB_WRITE_CONCERN=majority + +# Dragonfly - Redis Replacement (caching and events) +DRAGONFLY_HOST=${DRAGONFLY_HOST} +DRAGONFLY_PORT=${DRAGONFLY_PORT:-6379} +DRAGONFLY_PASSWORD=${DRAGONFLY_PASSWORD} +DRAGONFLY_USERNAME=${DRAGONFLY_USERNAME} +DRAGONFLY_DATABASE=0 +DRAGONFLY_MAX_RETRIES=5 +DRAGONFLY_RETRY_DELAY=100 +DRAGONFLY_CONNECT_TIMEOUT=30000 +DRAGONFLY_COMMAND_TIMEOUT=10000 +DRAGONFLY_POOL_SIZE=50 +DRAGONFLY_POOL_MIN=5 +DRAGONFLY_POOL_MAX=100 +DRAGONFLY_TLS=true +DRAGONFLY_ENABLE_KEEPALIVE=true +DRAGONFLY_KEEPALIVE_INTERVAL=30 +DRAGONFLY_CLUSTER_MODE=false +DRAGONFLY_CLUSTER_NODES= +DRAGONFLY_MAX_MEMORY=8gb +DRAGONFLY_CACHE_MODE=true + +# ======================================================================= +# MONITORING & LOGGING CONFIGURATIONS +# ======================================================================= + +# Logging Configuration (Production - Less verbose) +LOG_LEVEL=info +LOG_FORMAT=json +LOG_CONSOLE=false +LOG_FILE=true +LOG_FILE_PATH=/var/log/stock-bot +LOG_FILE_MAX_SIZE=100m +LOG_FILE_MAX_FILES=30 +LOG_FILE_DATE_PATTERN=YYYY-MM-DD +LOG_ERROR_FILE=true +LOG_ERROR_STACK=false +LOG_PERFORMANCE=true +LOG_SQL_QUERIES=false +LOG_HTTP_REQUESTS=false +LOG_STRUCTURED=true +LOG_TIMESTAMP=true +LOG_CALLER_INFO=false +LOG_SILENT_MODULES= +LOG_VERBOSE_MODULES= +LOG_SERVICE_NAME=stock-bot +LOG_SERVICE_VERSION=1.0.0 +LOG_ENVIRONMENT=production + +# Loki - Log Aggregation +LOKI_HOST=${LOKI_HOST} +LOKI_PORT=${LOKI_PORT:-3100} +LOKI_URL=${LOKI_URL} +LOKI_USERNAME=${LOKI_USERNAME} +LOKI_PASSWORD=${LOKI_PASSWORD} +LOKI_TENANT_ID=${LOKI_TENANT_ID} +LOKI_PUSH_TIMEOUT=30000 +LOKI_BATCH_SIZE=2048 +LOKI_BATCH_WAIT=5000 +LOKI_RETENTION_PERIOD=90d +LOKI_MAX_CHUNK_AGE=2h +LOKI_TLS_ENABLED=true +LOKI_TLS_INSECURE=false +LOKI_DEFAULT_LABELS= +LOKI_SERVICE_LABEL=stock-bot +LOKI_ENVIRONMENT_LABEL=production + +# Prometheus - Metrics Collection +PROMETHEUS_HOST=${PROMETHEUS_HOST} +PROMETHEUS_PORT=${PROMETHEUS_PORT:-9090} +PROMETHEUS_URL=${PROMETHEUS_URL} +PROMETHEUS_USERNAME=${PROMETHEUS_USERNAME} +PROMETHEUS_PASSWORD=${PROMETHEUS_PASSWORD} +PROMETHEUS_SCRAPE_INTERVAL=30s +PROMETHEUS_EVALUATION_INTERVAL=30s +PROMETHEUS_RETENTION_TIME=90d +PROMETHEUS_TLS_ENABLED=true +PROMETHEUS_TLS_INSECURE=false + +# Grafana - Visualization +GRAFANA_HOST=${GRAFANA_HOST} +GRAFANA_PORT=${GRAFANA_PORT:-3000} +GRAFANA_URL=${GRAFANA_URL} +GRAFANA_ADMIN_USER=${GRAFANA_ADMIN_USER} +GRAFANA_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD} +GRAFANA_ALLOW_SIGN_UP=false +GRAFANA_SECRET_KEY=${GRAFANA_SECRET_KEY} +GRAFANA_DATABASE_TYPE=postgres +GRAFANA_DATABASE_URL=${GRAFANA_DATABASE_URL} +GRAFANA_DISABLE_GRAVATAR=true +GRAFANA_ENABLE_GZIP=true + +# ======================================================================= +# DATA PROVIDER CONFIGURATIONS +# ======================================================================= + +# Default Data Provider +DEFAULT_DATA_PROVIDER=alpaca + +# Alpaca Markets (Production) +ALPACA_ENABLED=true +ALPACA_API_KEY=${ALPACA_API_KEY} +ALPACA_SECRET_KEY=${ALPACA_SECRET_KEY} +ALPACA_BASE_URL=https://api.alpaca.markets +ALPACA_DATA_URL=https://data.alpaca.markets +ALPACA_PAPER_TRADING=false + +# Polygon.io +POLYGON_ENABLED=${POLYGON_ENABLED:-false} +POLYGON_API_KEY=${POLYGON_API_KEY} +POLYGON_BASE_URL=https://api.polygon.io + +# Yahoo Finance +YAHOO_ENABLED=${YAHOO_ENABLED:-false} +YAHOO_BASE_URL=https://query1.finance.yahoo.com + +# IEX Cloud +IEX_ENABLED=${IEX_ENABLED:-false} +IEX_API_KEY=${IEX_API_KEY} +IEX_BASE_URL=https://cloud.iexapis.com + +# Data Provider Settings (Production) +DATA_PROVIDER_TIMEOUT=60000 +DATA_PROVIDER_RETRIES=5 +DATA_PROVIDER_RETRY_DELAY=2000 +DATA_CACHE_ENABLED=true +DATA_CACHE_TTL=60 +DATA_CACHE_MAX_SIZE=10000 + +# ======================================================================= +# TRADING & RISK MANAGEMENT (Production) +# ======================================================================= + +# Trading Configuration +PAPER_TRADING=false +MAX_POSITION_SIZE=${MAX_POSITION_SIZE:-0.05} +MAX_DAILY_LOSS=${MAX_DAILY_LOSS:-10000} + +# Risk Management (Stricter for production) +RISK_MAX_POSITION_SIZE=${RISK_MAX_POSITION_SIZE:-0.10} +RISK_MAX_LEVERAGE=${RISK_MAX_LEVERAGE:-1.5} +RISK_DEFAULT_STOP_LOSS=${RISK_DEFAULT_STOP_LOSS:-0.015} +RISK_DEFAULT_TAKE_PROFIT=${RISK_DEFAULT_TAKE_PROFIT:-0.045} +RISK_MAX_DRAWDOWN=${RISK_MAX_DRAWDOWN:-0.05} +RISK_MAX_CONSECUTIVE_LOSSES=${RISK_MAX_CONSECUTIVE_LOSSES:-3} +RISK_POSITION_SIZING_METHOD=volatility_adjusted +RISK_CIRCUIT_BREAKER_ENABLED=true +RISK_CIRCUIT_BREAKER_THRESHOLD=0.02 +RISK_CIRCUIT_BREAKER_COOLDOWN=7200000 +RISK_ALLOW_WEEKEND_TRADING=false +RISK_MARKET_HOURS_ONLY=true + +# ======================================================================= +# FEATURE FLAGS (Production) +# ======================================================================= +ENABLE_ML_SIGNALS=${ENABLE_ML_SIGNALS:-false} +ENABLE_SENTIMENT_ANALYSIS=${ENABLE_SENTIMENT_ANALYSIS:-false} +ENABLE_SOCIAL_SIGNALS=${ENABLE_SOCIAL_SIGNALS:-false} +ENABLE_OPTIONS_TRADING=${ENABLE_OPTIONS_TRADING:-false} +ENABLE_CRYPTO_TRADING=${ENABLE_CRYPTO_TRADING:-false} +ENABLE_BACKTESTING=true +ENABLE_PAPER_TRADING=false +ENABLE_LIVE_TRADING=true + +# ======================================================================= +# PRODUCTION SETTINGS +# ======================================================================= +DEBUG_MODE=false +VERBOSE_LOGGING=false +MOCK_DATA_PROVIDERS=false +ENABLE_API_RATE_LIMITING=true +ENABLE_REQUEST_LOGGING=false diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..dfd7a4d --- /dev/null +++ b/.env.production @@ -0,0 +1,135 @@ +# Production Environment Configuration +NODE_ENV=production +PORT=3001 + +# ============================================================================= +# DATABASE CONFIGURATIONS +# ============================================================================= + +# PostgreSQL - Operational data (orders, positions, strategies) +DB_HOST=postgres +DB_PORT=5432 +DB_NAME=trading_bot +DB_USER=trading_user +DB_PASSWORD=${POSTGRES_PASSWORD} +DB_POOL_MIN=5 +DB_POOL_MAX=20 +DB_SSL=true +DB_SSL_REJECT_UNAUTHORIZED=true + +# QuestDB - Time-series data (OHLCV, indicators, performance) +QUESTDB_HOST=questdb +QUESTDB_HTTP_PORT=9000 +QUESTDB_PG_PORT=8812 +QUESTDB_INFLUX_PORT=9009 +QUESTDB_DEFAULT_DATABASE=qdb +QUESTDB_TELEMETRY_ENABLED=false +QUESTDB_TLS_ENABLED=true + +# MongoDB - Document storage (sentiment, raw docs, unstructured data) +MONGODB_HOST=mongodb +MONGODB_PORT=27017 +MONGODB_DATABASE=trading_documents +MONGODB_USERNAME=${MONGODB_ROOT_USERNAME} +MONGODB_PASSWORD=${MONGODB_ROOT_PASSWORD} +MONGODB_AUTH_SOURCE=admin +MONGODB_TLS=true +MONGODB_RETRY_WRITES=true + +# Dragonfly - Redis replacement for caching and events +DRAGONFLY_HOST=dragonfly +DRAGONFLY_PORT=6379 +DRAGONFLY_PASSWORD=${DRAGONFLY_PASSWORD} +DRAGONFLY_DATABASE=0 +DRAGONFLY_MAX_MEMORY=4gb +DRAGONFLY_CACHE_MODE=true +DRAGONFLY_TLS=true + +# ============================================================================= +# MONITORING & OBSERVABILITY +# ============================================================================= + +# Prometheus - Metrics collection +PROMETHEUS_HOST=prometheus +PROMETHEUS_PORT=9090 +PROMETHEUS_SCRAPE_INTERVAL=30s +PROMETHEUS_RETENTION_TIME=90d +PROMETHEUS_TLS_ENABLED=true + +# Grafana - Visualization +GRAFANA_HOST=grafana +GRAFANA_PORT=3000 +GRAFANA_ADMIN_USER=${GRAFANA_ADMIN_USER} +GRAFANA_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD} +GRAFANA_ALLOW_SIGN_UP=false +GRAFANA_SECRET_KEY=${GRAFANA_SECRET_KEY} +GRAFANA_DATABASE_TYPE=postgres +GRAFANA_DISABLE_GRAVATAR=true + +# Loki - Log aggregation +LOKI_HOST=loki +LOKI_PORT=3100 +LOKI_RETENTION_PERIOD=90d +LOKI_BATCH_SIZE=2048 +LOKI_TLS_ENABLED=true + +# ============================================================================= +# ADMIN INTERFACES (Disabled in production) +# ============================================================================= + +# PgAdmin - PostgreSQL GUI (disabled in production) +PGADMIN_HOST=pgadmin +PGADMIN_PORT=8080 +PGADMIN_DEFAULT_EMAIL=${PGADMIN_EMAIL} +PGADMIN_DEFAULT_PASSWORD=${PGADMIN_PASSWORD} +PGADMIN_SERVER_MODE=true +PGADMIN_MASTER_PASSWORD_REQUIRED=true + +# Mongo Express - MongoDB GUI (disabled in production) +MONGO_EXPRESS_HOST=mongo-express +MONGO_EXPRESS_PORT=8081 +MONGO_EXPRESS_MONGODB_SERVER=mongodb +MONGO_EXPRESS_BASICAUTH_USERNAME=${MONGO_EXPRESS_USER} +MONGO_EXPRESS_BASICAUTH_PASSWORD=${MONGO_EXPRESS_PASSWORD} + +# Redis Insight - Dragonfly/Redis GUI (disabled in production) +REDIS_INSIGHT_HOST=redis-insight +REDIS_INSIGHT_PORT=8001 +REDIS_INSIGHT_REDIS_HOSTS=production:dragonfly:6379 + +# ============================================================================= +# DATA PROVIDERS & TRADING +# ============================================================================= + +# API Keys (Set from environment variables) +ALPHA_VANTAGE_API_KEY=${ALPHA_VANTAGE_API_KEY} +ALPACA_API_KEY=${ALPACA_API_KEY} +ALPACA_SECRET_KEY=${ALPACA_SECRET_KEY} +POLYGON_API_KEY=${POLYGON_API_KEY} +IEX_API_KEY=${IEX_API_KEY} +YAHOO_FINANCE_API_KEY=${YAHOO_FINANCE_API_KEY} + +# Trading Configuration +PAPER_TRADING=false +MAX_POSITION_SIZE=0.05 +MAX_DAILY_LOSS=5000 +RISK_MANAGEMENT_ENABLED=true + +# ============================================================================= +# APPLICATION SETTINGS +# ============================================================================= + +# Logging +LOG_LEVEL=info +LOG_FORMAT=json + +# Feature Flags +ENABLE_ML_SIGNALS=true +ENABLE_SENTIMENT_ANALYSIS=true +ENABLE_RISK_MONITORING=true +ENABLE_PERFORMANCE_TRACKING=true + +# Security +CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS} +JWT_SECRET=${JWT_SECRET} +API_RATE_LIMIT=1000 diff --git a/bun.lock b/bun.lock index d5cc489..9d3e2f1 100644 --- a/bun.lock +++ b/bun.lock @@ -3,6 +3,9 @@ "workspaces": { "": { "name": "stock-bot", + "dependencies": { + "valibot": "^1.1.0", + }, "devDependencies": { "@types/node": "^20.12.12", "turbo": "^2.5.4", @@ -243,6 +246,7 @@ "version": "1.0.0", "dependencies": { "dotenv": "^16.3.1", + "envalid": "^8.0.0", "zod": "^3.22.4", }, "devDependencies": { @@ -1135,6 +1139,8 @@ "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + "envalid": ["envalid@8.0.0", "", { "dependencies": { "tslib": "2.6.2" } }, "sha512-PGeYJnJB5naN0ME6SH8nFcDj9HVbLpYIfg1p5lAyM9T4cH2lwtu2fLbozC/bq+HUUOIFxhX/LP0/GmlqPHT4tQ=="], + "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], @@ -1985,6 +1991,8 @@ "v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="], + "valibot": ["valibot@1.1.0", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw=="], + "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], "validate-npm-package-name": ["validate-npm-package-name@6.0.0", "", {}, "sha512-d7KLgL1LD3U3fgnvWEY1cQXoO/q6EQ1BSz48Sa149V/5zVTAbgmZIpyI8TRi6U9/JNyeYLlTKsEMPtLC27RFUg=="], @@ -2141,6 +2149,8 @@ "ent/punycode": ["punycode@1.4.1", "", {}, "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="], + "envalid/tslib": ["tslib@2.6.2", "", {}, "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="], + "execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], "external-editor/tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="], diff --git a/docker-compose.monitoring.yml b/docker-compose.monitoring.yml deleted file mode 100644 index e24847c..0000000 --- a/docker-compose.monitoring.yml +++ /dev/null @@ -1,46 +0,0 @@ -version: '3.8' - -services: - # Loki - Log aggregation - loki: - image: grafana/loki:2.9.2 - container_name: trading-bot-loki - ports: - - "3100:3100" - volumes: - - loki_data:/loki - - ./monitoring/loki:/etc/loki - command: -config.file=/etc/loki/loki-config.yaml - healthcheck: - test: ["CMD", "wget", "-q", "--spider", "http://localhost:3100/ready"] - interval: 30s - timeout: 10s - retries: 3 - networks: - - trading-bot-network - - # Grafana - Visualization for logs and metrics - grafana: - image: grafana/grafana:10.2.0 - container_name: trading-bot-grafana - ports: - - "3000:3000" - environment: - - GF_SECURITY_ADMIN_PASSWORD=admin - - GF_SECURITY_ADMIN_USER=admin - - GF_PATHS_PROVISIONING=/etc/grafana/provisioning - volumes: - - grafana_data:/var/lib/grafana - - ./monitoring/grafana/provisioning:/etc/grafana/provisioning - depends_on: - - loki - networks: - - trading-bot-network - -volumes: - loki_data: - grafana_data: - -networks: - trading-bot-network: - external: true diff --git a/docker-compose.monitoring.yml.old b/docker-compose.monitoring.yml.old deleted file mode 100644 index e24847c..0000000 --- a/docker-compose.monitoring.yml.old +++ /dev/null @@ -1,46 +0,0 @@ -version: '3.8' - -services: - # Loki - Log aggregation - loki: - image: grafana/loki:2.9.2 - container_name: trading-bot-loki - ports: - - "3100:3100" - volumes: - - loki_data:/loki - - ./monitoring/loki:/etc/loki - command: -config.file=/etc/loki/loki-config.yaml - healthcheck: - test: ["CMD", "wget", "-q", "--spider", "http://localhost:3100/ready"] - interval: 30s - timeout: 10s - retries: 3 - networks: - - trading-bot-network - - # Grafana - Visualization for logs and metrics - grafana: - image: grafana/grafana:10.2.0 - container_name: trading-bot-grafana - ports: - - "3000:3000" - environment: - - GF_SECURITY_ADMIN_PASSWORD=admin - - GF_SECURITY_ADMIN_USER=admin - - GF_PATHS_PROVISIONING=/etc/grafana/provisioning - volumes: - - grafana_data:/var/lib/grafana - - ./monitoring/grafana/provisioning:/etc/grafana/provisioning - depends_on: - - loki - networks: - - trading-bot-network - -volumes: - loki_data: - grafana_data: - -networks: - trading-bot-network: - external: true diff --git a/docker-compose.yml.backup b/docker-compose.yml.backup deleted file mode 100644 index 1b0562a..0000000 --- a/docker-compose.yml.backup +++ /dev/null @@ -1,244 +0,0 @@ -services: - # Dragonfly - Redis replacement for caching and events - dragonfly: - image: docker.dragonflydb.io/dragonflydb/dragonfly:latest - container_name: trading-bot-dragonfly - ports: - - "6379:6379" - command: - - dragonfly - - --logtostderr - - --cache_mode=true - - --maxmemory=2gb - - --proactor_threads=8 - - --bind=0.0.0.0 - volumes: - - dragonfly_data:/data - restart: unless-stopped - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 30s - timeout: 10s - retries: 3 - networks: - - trading-bot-network - - # PostgreSQL - Operational data (orders, positions, strategies) - postgres: - image: postgres:16-alpine - container_name: trading-bot-postgres - environment: - POSTGRES_DB: trading_bot - POSTGRES_USER: trading_user - POSTGRES_PASSWORD: trading_pass_dev - POSTGRES_INITDB_ARGS: "--encoding=UTF-8" - ports: - - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - - ./database/postgres/init:/docker-entrypoint-initdb.d - restart: unless-stopped - healthcheck: - test: ["CMD-SHELL", "pg_isready -U trading_user -d trading_bot"] - interval: 30s - timeout: 10s - retries: 3 - networks: - - trading-bot-network - - # QuestDB - Time-series data (OHLCV, indicators, performance) - questdb: - image: questdb/questdb:latest - container_name: trading-bot-questdb - ports: - - "9000:9000" # Web console - - "8812:8812" # PostgreSQL wire protocol - - "9009:9009" # InfluxDB line protocol - volumes: - - questdb_data:/var/lib/questdb - environment: - - QDB_TELEMETRY_ENABLED=false - restart: unless-stopped - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:9000/status"] - interval: 30s - timeout: 10s - retries: 3 - networks: - - trading-bot-network - - # MongoDB - Document storage (sentiment, raw docs, unstructured data) - mongodb: - image: mongo:7-jammy - container_name: trading-bot-mongodb - environment: - MONGO_INITDB_ROOT_USERNAME: trading_admin - MONGO_INITDB_ROOT_PASSWORD: trading_mongo_dev - MONGO_INITDB_DATABASE: trading_documents - ports: - - "27017:27017" - volumes: - - mongodb_data:/data/db - - ./database/mongodb/init:/docker-entrypoint-initdb.d - restart: unless-stopped - healthcheck: - test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] - interval: 30s - timeout: 10s - retries: 3 - networks: - - trading-bot-network - - # Redis Insight - GUI for Dragonfly debugging - redis-insight: - image: redislabs/redisinsight:latest - container_name: trading-bot-redis-insight - ports: - - "8001:8001" - environment: - - REDIS_HOSTS=local:dragonfly:6379 - depends_on: - - dragonfly - restart: unless-stopped - networks: - - trading-bot-network - - # PgAdmin - PostgreSQL GUI - pgadmin: - image: dpage/pgadmin4:latest - container_name: trading-bot-pgadmin - environment: - PGADMIN_DEFAULT_EMAIL: boki@stare.gg - PGADMIN_DEFAULT_PASSWORD: admin123 - PGADMIN_CONFIG_SERVER_MODE: 'False' - PGADMIN_DISABLE_POSTFIX: 'true' - ports: - - "8080:80" - volumes: - - pgadmin_data:/var/lib/pgadmin - depends_on: - - postgres - restart: unless-stopped - networks: - - trading-bot-network - - # Mongo Express - MongoDB GUI - mongo-express: - image: mongo-express:latest - container_name: trading-bot-mongo-express - environment: - ME_CONFIG_MONGODB_ADMINUSERNAME: trading_admin - ME_CONFIG_MONGODB_ADMINPASSWORD: trading_mongo_dev - ME_CONFIG_MONGODB_SERVER: mongodb - ME_CONFIG_MONGODB_PORT: 27017 - ME_CONFIG_BASICAUTH_USERNAME: boki - ME_CONFIG_BASICAUTH_PASSWORD: admin123 - ports: - - "8081:8081" - depends_on: - - mongodb - restart: unless-stopped - networks: - - trading-bot-network - - # Prometheus - Metrics collection (optional) - prometheus: - image: prom/prometheus:latest - container_name: trading-bot-prometheus - ports: - - "9090:9090" - volumes: - - ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - - prometheus_data:/prometheus - command: - - '--config.file=/etc/prometheus/prometheus.yml' - - '--storage.tsdb.path=/prometheus' - - '--web.console.libraries=/etc/prometheus/console_libraries' - - '--web.console.templates=/etc/prometheus/consoles' - - '--web.enable-lifecycle' - restart: unless-stopped - networks: - - trading-bot-network - # Loki - Log aggregation - loki: - image: grafana/loki:2.9.2 - container_name: trading-bot-loki - ports: - - "3100:3100" - volumes: - - loki_data:/loki - - ./monitoring/loki:/etc/loki - command: -config.file=/etc/loki/loki-config.yaml - healthcheck: - test: ["CMD", "wget", "-q", "--spider", "http://localhost:3100/ready"] - interval: 30s - timeout: 10s - retries: 3 - restart: unless-stopped - networks: - - trading-bot-network - - # Grafana - Visualization for logs and metrics - grafana: - image: grafana/grafana:10.2.0 - container_name: trading-bot-grafana - ports: - - "3000:3000" - environment: - - GF_SECURITY_ADMIN_PASSWORD=admin - - GF_SECURITY_ADMIN_USER=admin - - GF_PATHS_PROVISIONING=/etc/grafana/provisioning - - GF_USERS_ALLOW_SIGN_UP=false - volumes: - - grafana_data:/var/lib/grafana - - ./monitoring/grafana/provisioning:/etc/grafana/provisioning - depends_on: - - prometheus - - loki - restart: unless-stopped - networks: - - trading-bot-network - -volumes: - postgres_data: - questdb_data: - dragonfly_data: - mongodb_data: - pgadmin_data: - prometheus_data: - grafana_data: - loki_data: - - "3100:3100" - volumes: - - loki_data:/loki - - ./monitoring/loki:/etc/loki - command: -config.file=/etc/loki/loki-config.yaml - healthcheck: - test: ["CMD", "wget", "-q", "--spider", "http://localhost:3100/ready"] - interval: 30s - timeout: 10s - retries: 3 - networks: - - trading-bot-network - - # Grafana - Visualization for logs and metrics - grafana: - image: grafana/grafana:10.2.0 - container_name: trading-bot-grafana - ports: - - "3000:3000" - environment: - - GF_SECURITY_ADMIN_PASSWORD=admin - - GF_SECURITY_ADMIN_USER=admin - - GF_PATHS_PROVISIONING=/etc/grafana/provisioning - volumes: - - grafana_data:/var/lib/grafana - - ./monitoring/grafana/provisioning:/etc/grafana/provisioning - depends_on: - - loki - networks: - - trading-bot-network - -networks: - trading-bot-network: - driver: bridge diff --git a/docker-compose.yml.new b/docker-compose.yml.new deleted file mode 100644 index 265e0aa..0000000 --- a/docker-compose.yml.new +++ /dev/null @@ -1,217 +0,0 @@ -version: '3.8' - -services: - # Dragonfly - Redis replacement for caching and events - dragonfly: - image: docker.dragonflydb.io/dragonflydb/dragonfly:latest - container_name: trading-bot-dragonfly - ports: - - "6379:6379" - command: - - dragonfly - - --logtostderr - - --cache_mode=true - - --maxmemory=2gb - - --proactor_threads=8 - - --bind=0.0.0.0 - volumes: - - dragonfly_data:/data - restart: unless-stopped - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 30s - timeout: 10s - retries: 3 - networks: - - trading-bot-network - - # PostgreSQL - Operational data (orders, positions, strategies) - postgres: - image: postgres:16-alpine - container_name: trading-bot-postgres - environment: - POSTGRES_DB: trading_bot - POSTGRES_USER: trading_user - POSTGRES_PASSWORD: trading_pass_dev - POSTGRES_INITDB_ARGS: "--encoding=UTF-8" - ports: - - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - - ./database/postgres/init:/docker-entrypoint-initdb.d - restart: unless-stopped - healthcheck: - test: ["CMD-SHELL", "pg_isready -U trading_user -d trading_bot"] - interval: 30s - timeout: 10s - retries: 3 - networks: - - trading-bot-network - - # QuestDB - Time-series data (OHLCV, indicators, performance) - questdb: - image: questdb/questdb:latest - container_name: trading-bot-questdb - ports: - - "9000:9000" # Web console - - "8812:8812" # PostgreSQL wire protocol - - "9009:9009" # InfluxDB line protocol - volumes: - - questdb_data:/var/lib/questdb - environment: - - QDB_TELEMETRY_ENABLED=false - restart: unless-stopped - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:9000/status"] - interval: 30s - timeout: 10s - retries: 3 - networks: - - trading-bot-network - - # MongoDB - Document storage (sentiment, raw docs, unstructured data) - mongodb: - image: mongo:7-jammy - container_name: trading-bot-mongodb - environment: - MONGO_INITDB_ROOT_USERNAME: trading_admin - MONGO_INITDB_ROOT_PASSWORD: trading_mongo_dev - MONGO_INITDB_DATABASE: trading_documents - ports: - - "27017:27017" - volumes: - - mongodb_data:/data/db - - ./database/mongodb/init:/docker-entrypoint-initdb.d - restart: unless-stopped - healthcheck: - test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] - interval: 30s - timeout: 10s - retries: 3 - networks: - - trading-bot-network - - # Redis Insight - GUI for Dragonfly debugging - redis-insight: - image: redislabs/redisinsight:latest - container_name: trading-bot-redis-insight - ports: - - "8001:8001" - environment: - - REDIS_HOSTS=local:dragonfly:6379 - depends_on: - - dragonfly - restart: unless-stopped - networks: - - trading-bot-network - - # PgAdmin - PostgreSQL GUI - pgadmin: - image: dpage/pgadmin4:latest - container_name: trading-bot-pgadmin - environment: - PGADMIN_DEFAULT_EMAIL: admin@tradingbot.local - PGADMIN_DEFAULT_PASSWORD: admin123 - PGADMIN_CONFIG_SERVER_MODE: 'False' - PGADMIN_DISABLE_POSTFIX: 'true' - ports: - - "8080:80" - volumes: - - pgadmin_data:/var/lib/pgadmin - depends_on: - - postgres - restart: unless-stopped - networks: - - trading-bot-network - - # Mongo Express - MongoDB GUI - mongo-express: - image: mongo-express:latest - container_name: trading-bot-mongo-express - environment: - ME_CONFIG_MONGODB_ADMINUSERNAME: trading_admin - ME_CONFIG_MONGODB_ADMINPASSWORD: trading_mongo_dev - ME_CONFIG_MONGODB_SERVER: mongodb - ME_CONFIG_MONGODB_PORT: 27017 - ME_CONFIG_BASICAUTH_USERNAME: admin - ME_CONFIG_BASICAUTH_PASSWORD: admin123 - ports: - - "8081:8081" - depends_on: - - mongodb - restart: unless-stopped - networks: - - trading-bot-network - - # Prometheus - Metrics collection - prometheus: - image: prom/prometheus:latest - container_name: trading-bot-prometheus - ports: - - "9090:9090" - volumes: - - ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - - prometheus_data:/prometheus - command: - - '--config.file=/etc/prometheus/prometheus.yml' - - '--storage.tsdb.path=/prometheus' - - '--web.console.libraries=/etc/prometheus/console_libraries' - - '--web.console.templates=/etc/prometheus/consoles' - - '--web.enable-lifecycle' - restart: unless-stopped - networks: - - trading-bot-network - - # Loki - Log aggregation - loki: - image: grafana/loki:2.9.2 - container_name: trading-bot-loki - ports: - - "3100:3100" - volumes: - - loki_data:/loki - - ./monitoring/loki:/etc/loki - command: -config.file=/etc/loki/loki-config.yaml - healthcheck: - test: ["CMD", "wget", "-q", "--spider", "http://localhost:3100/ready"] - interval: 30s - timeout: 10s - retries: 3 - restart: unless-stopped - networks: - - trading-bot-network - - # Grafana - Visualization for logs and metrics - grafana: - image: grafana/grafana:10.2.0 - container_name: trading-bot-grafana - ports: - - "3000:3000" - environment: - - GF_SECURITY_ADMIN_PASSWORD=admin - - GF_SECURITY_ADMIN_USER=admin - - GF_PATHS_PROVISIONING=/etc/grafana/provisioning - - GF_USERS_ALLOW_SIGN_UP=false - volumes: - - grafana_data:/var/lib/grafana - - ./monitoring/grafana/provisioning:/etc/grafana/provisioning - depends_on: - - prometheus - - loki - restart: unless-stopped - networks: - - trading-bot-network - -volumes: - postgres_data: - questdb_data: - dragonfly_data: - mongodb_data: - pgadmin_data: - prometheus_data: - grafana_data: - loki_data: - -networks: - trading-bot-network: - driver: bridge diff --git a/docs/MULTI_DATABASE_ARCHITECTURE.md b/docs/MULTI_DATABASE_ARCHITECTURE.md new file mode 100644 index 0000000..88d7071 --- /dev/null +++ b/docs/MULTI_DATABASE_ARCHITECTURE.md @@ -0,0 +1,238 @@ +# Stock Bot Multi-Database Architecture Documentation + +## Overview + +The Stock Bot platform uses a sophisticated multi-database architecture designed to handle different types of data efficiently. This document outlines the configuration system, database choices, and monitoring setup. + +## Configuration System + +### Migration from Custom Config to Envalid + +The platform has migrated from a complex Valibot-based configuration system to a simpler, more maintainable **envalid** approach: + +```typescript +// New configuration pattern used throughout +export const configName = cleanEnv(process.env, { + ENV_VAR: str({ default: 'value', desc: 'Description' }), + NUMERIC_VAR: num({ default: 3000, desc: 'Port number' }), + BOOLEAN_VAR: bool({ default: false, desc: 'Feature flag' }), +}); +``` + +### Configuration Modules + +| Module | Purpose | File | +|--------|---------|------| +| `database` | PostgreSQL operational data | `libs/config/src/database.ts` | +| `questdb` | Time-series data storage | `libs/config/src/questdb.ts` | +| `mongodb` | Document and unstructured data | `libs/config/src/mongodb.ts` | +| `dragonfly` | Caching and event streaming | `libs/config/src/dragonfly.ts` | +| `monitoring` | Prometheus and Grafana | `libs/config/src/monitoring.ts` | +| `loki` | Log aggregation | `libs/config/src/loki.ts` | +| `logging` | Application logging | `libs/config/src/logging.ts` | + +## Database Architecture + +### 1. PostgreSQL - Operational Data Store +**Purpose**: Primary relational database for structured operational data +- **Data Types**: Orders, positions, strategies, user accounts, trading rules +- **Strengths**: ACID compliance, complex queries, transactions +- **Configuration**: `libs/config/src/database.ts` + +```typescript +// Example usage +import { databaseConfig } from '@trading-bot/config'; +// Connects to operational PostgreSQL instance +``` + +### 2. QuestDB - Time-Series Database +**Purpose**: High-performance time-series data storage +- **Data Types**: OHLCV data, technical indicators, performance metrics, tick data +- **Strengths**: Fast ingestion, SQL queries on time-series, columnar storage +- **Configuration**: `libs/config/src/questdb.ts` + +```typescript +// Example usage +import { questdbConfig } from '@trading-bot/config'; +// Optimized for time-series queries and analytics +``` + +### 3. MongoDB - Document Store +**Purpose**: Flexible document storage for unstructured data +- **Data Types**: Market sentiment, news articles, research reports, ML model outputs +- **Strengths**: Schema flexibility, horizontal scaling, complex document queries +- **Configuration**: `libs/config/src/mongodb.ts` + +```typescript +// Example usage +import { mongodbConfig } from '@trading-bot/config'; +// Handles variable schema and complex nested data +``` + +### 4. Dragonfly - Cache & Event Store +**Purpose**: High-performance caching and real-time event streaming +- **Data Types**: Market data cache, session data, real-time events, pub/sub messages +- **Strengths**: Redis compatibility, better performance, memory efficiency +- **Configuration**: `libs/config/src/dragonfly.ts` + +```typescript +// Example usage +import { dragonflyConfig } from '@trading-bot/config'; +// Drop-in Redis replacement with better performance +``` + +## Monitoring & Observability Stack + +### Prometheus - Metrics Collection +- **Purpose**: Time-series metrics and monitoring +- **Metrics**: System performance, trading metrics, database metrics +- **Configuration**: `libs/config/src/monitoring.ts` + +### Grafana - Visualization +- **Purpose**: Dashboards and alerting +- **Dashboards**: Trading performance, system health, database monitoring +- **Configuration**: `libs/config/src/monitoring.ts` + +### Loki - Log Aggregation +- **Purpose**: Centralized log collection and analysis +- **Logs**: Application logs, database logs, system logs +- **Configuration**: `libs/config/src/loki.ts` + +### Application Logging +- **Purpose**: Structured application logging +- **Features**: Multiple formats, file rotation, log levels +- **Configuration**: `libs/config/src/logging.ts` + +## Environment Files + +### `.env` - Development (Local) +- **Purpose**: Local development with services on localhost +- **Databases**: All services running on localhost with standard ports +- **Logging**: Pretty-formatted console output + +### `.env.docker` - Docker Compose +- **Purpose**: Container orchestration with Docker Compose +- **Databases**: Container names as hostnames (e.g., `postgres`, `mongodb`) +- **Features**: Health checks, resource limits, volume mounts + +### `.env.complete` - Full Development +- **Purpose**: Complete feature set for development testing +- **Features**: All services enabled, verbose logging, debug mode +- **Use Case**: Testing the full platform locally + +### `.env.prod` - Production +- **Purpose**: Production deployment configuration +- **Security**: Environment variable references, secure defaults +- **Features**: Optimized logging, monitoring enabled + +## Admin Interfaces + +### PgAdmin - PostgreSQL Management +- **URL**: http://localhost:8080 (development) +- **Purpose**: Database administration, query execution, monitoring + +### Mongo Express - MongoDB Management +- **URL**: http://localhost:8081 (development) +- **Purpose**: Document browsing, collection management, query testing + +### Redis Insight - Dragonfly/Redis Management +- **URL**: http://localhost:8001 (development) +- **Purpose**: Cache monitoring, key browsing, performance analysis + +## Data Flow Architecture + +``` +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ Market Data │───▶│ Dragonfly │───▶│ QuestDB │ +│ Feed │ │ (Cache/Events) │ │ (Time-Series) │ +└─────────────────┘ └──────────────────┘ └─────────────────┘ + │ + ▼ +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ Trading │◀──▶│ PostgreSQL │ │ MongoDB │ +│ Engine │ │ (Operational) │ │ (Documents) │ +└─────────────────┘ └──────────────────┘ └─────────────────┘ + │ ▲ + ▼ │ +┌─────────────────┐ ┌──────────────────┐ │ +│ Monitoring │◀───│ Prometheus │ │ +│ Dashboard │ │ (Metrics) │ │ +└─────────────────┘ └──────────────────┘ │ + │ +┌─────────────────┐ ┌──────────────────┐ │ +│ Log Analysis │◀───│ Loki │───────────┘ +│ │ │ (Logs) │ +└─────────────────┘ └──────────────────┘ +``` + +## Best Practices + +### Database Selection Guidelines + +1. **PostgreSQL**: Use for transactional data requiring ACID properties + - Orders, positions, account balances, strategy configurations + +2. **QuestDB**: Use for time-series data requiring fast analytics + - OHLCV data, technical indicators, performance metrics + +3. **MongoDB**: Use for flexible, document-based data + - Market sentiment, news articles, ML model outputs + +4. **Dragonfly**: Use for temporary data requiring fast access + - Real-time market data cache, session data, event streams + +### Configuration Best Practices + +1. **Environment Separation**: Use appropriate `.env` file for each environment +2. **Security**: Never commit sensitive credentials to version control +3. **Validation**: All configuration uses envalid for runtime validation +4. **Documentation**: Each config variable includes descriptive help text + +### Monitoring Best Practices + +1. **Metrics**: Monitor database performance, trading metrics, system health +2. **Logging**: Use structured logging with appropriate log levels +3. **Alerting**: Set up Grafana alerts for critical system metrics +4. **Log Retention**: Configure appropriate retention periods for each environment + +## Migration Guide + +If migrating from the old configuration system: + +1. **Update Imports**: Change from custom config to new envalid-based modules +2. **Environment Variables**: Update `.env` files to include all new services +3. **Docker Setup**: Use `.env.docker` for container-based deployments +4. **Monitoring**: Enable Prometheus, Grafana, and Loki for observability + +## Troubleshooting + +### Common Issues + +1. **Connection Failures**: Check container names in Docker environments +2. **Port Conflicts**: Verify port mappings in environment files +3. **Permission Errors**: Ensure proper database credentials and permissions +4. **Memory Issues**: Adjust resource limits in Docker configuration + +### Debug Commands + +```bash +# Check container status +docker-compose ps + +# View container logs +docker-compose logs [service-name] + +# Test database connections +docker-compose exec postgres pg_isready +docker-compose exec mongodb mongosh --eval "db.runCommand('ping')" +docker-compose exec questdb curl -f http://localhost:9000/status +docker-compose exec dragonfly redis-cli ping +``` + +## Future Enhancements + +1. **Database Sharding**: Implement horizontal scaling for high-volume data +2. **Read Replicas**: Add read replicas for improved query performance +3. **Backup Strategy**: Implement automated backup and recovery procedures +4. **Security**: Add encryption at rest and in transit +5. **Performance**: Implement connection pooling and query optimization diff --git a/libs/config/USAGE.md b/libs/config/USAGE.md new file mode 100644 index 0000000..cba7761 --- /dev/null +++ b/libs/config/USAGE.md @@ -0,0 +1,131 @@ +# Stock Bot Configuration Library Usage Guide + +This guide shows how to use the envalid-based configuration system in the Stock Bot platform. + +## Quick Start + +```typescript +import { databaseConfig, loggingConfig, riskConfig, dataProvidersConfig } from '@stock-bot/config'; + +// Access individual values +console.log(`Database: ${databaseConfig.DB_HOST}:${databaseConfig.DB_PORT}`); +console.log(`Log level: ${loggingConfig.LOG_LEVEL}`); +console.log(`Max position size: ${riskConfig.RISK_MAX_POSITION_SIZE}`); +``` + +## Environment Variables + +All configuration is driven by environment variables. You can set them in: +- `.env` files +- System environment variables +- Docker environment variables + +### Database Configuration +```bash +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=stockbot +DB_USER=stockbot +DB_PASSWORD=your_password +DB_SSL=false +DB_POOL_MAX=10 +``` + +### Logging Configuration +```bash +LOG_LEVEL=info +LOG_CONSOLE=true +LOKI_HOST=localhost +LOKI_PORT=3100 +LOKI_LABELS=service=market-data-gateway,version=1.0.0 +``` + +### Risk Management Configuration +```bash +RISK_MAX_POSITION_SIZE=0.1 +RISK_DEFAULT_STOP_LOSS=0.05 +RISK_DEFAULT_TAKE_PROFIT=0.15 +RISK_CIRCUIT_BREAKER_ENABLED=true +``` + +### Data Provider Configuration +```bash +DEFAULT_DATA_PROVIDER=alpaca +ALPACA_API_KEY=your_api_key +ALPACA_API_SECRET=your_api_secret +ALPACA_ENABLED=true +POLYGON_ENABLED=false +``` + +## Advanced Usage + +### Type Safety +All configurations are fully typed: + +```typescript +import type { DatabaseConfig, LoggingConfig, RiskConfig } from '@stock-bot/config'; + +function setupDatabase(config: DatabaseConfig) { + // TypeScript knows all the available properties + return { + host: config.DB_HOST, + port: config.DB_PORT, // number + ssl: config.DB_SSL, // boolean + }; +} +``` + +### Environment Detection +```typescript +import { getEnvironment, Environment } from '@stock-bot/config'; + +const env = getEnvironment(); +if (env === Environment.Production) { + // Production-specific logic +} +``` + +### Data Provider Helpers +```typescript +import { getProviderConfig, getEnabledProviders, getDefaultProvider } from '@stock-bot/config'; + +// Get specific provider +const alpaca = getProviderConfig('alpaca'); + +// Get all enabled providers +const providers = getEnabledProviders(); + +// Get default provider +const defaultProvider = getDefaultProvider(); +``` + +## Configuration Files + +The library consists of these modules: + +- **core.ts** - Core utilities and environment detection +- **database.ts** - Database connection settings +- **logging.ts** - Logging and Loki configuration +- **risk.ts** - Risk management parameters +- **data-providers.ts** - Data provider settings + +## Benefits of This Approach + +1. **Zero Configuration Schema** - No complex schema definitions needed +2. **Automatic Type Inference** - TypeScript types are generated automatically +3. **Environment Variable Validation** - Invalid values are caught at startup +4. **Great Developer Experience** - IntelliSense works perfectly +5. **Production Ready** - Used by many large-scale applications + +## Migration from Previous System + +If you're migrating from the old Valibot-based system: + +```typescript +// Old way +const config = createConfigLoader('database', databaseSchema, defaultConfig)(); + +// New way +import { databaseConfig } from '@stock-bot/config'; +// That's it! No schema needed, no validation needed, no complex setup. +``` diff --git a/libs/config/package.json b/libs/config/package.json index fb660b7..02e2968 100644 --- a/libs/config/package.json +++ b/libs/config/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "dotenv": "^16.3.1", + "envalid": "^8.0.0", "zod": "^3.22.4" }, "devDependencies": { diff --git a/libs/config/src/admin-interfaces.ts b/libs/config/src/admin-interfaces.ts new file mode 100644 index 0000000..4dc7e1b --- /dev/null +++ b/libs/config/src/admin-interfaces.ts @@ -0,0 +1,119 @@ +/** + * Admin interfaces configuration using envalid + * PgAdmin, Mongo Express, Redis Insight for database management + */ +import { cleanEnv, str, port, bool } from 'envalid'; + +/** + * PgAdmin configuration with validation and defaults + */ +export const pgAdminConfig = cleanEnv(process.env, { + // PgAdmin Server + PGADMIN_HOST: str({ default: 'localhost', desc: 'PgAdmin host' }), + PGADMIN_PORT: port({ default: 8080, desc: 'PgAdmin port' }), + + // Authentication + PGADMIN_DEFAULT_EMAIL: str({ default: 'admin@tradingbot.local', desc: 'PgAdmin default admin email' }), + PGADMIN_DEFAULT_PASSWORD: str({ default: 'admin123', desc: 'PgAdmin default admin password' }), + + // Configuration + PGADMIN_SERVER_MODE: bool({ default: false, desc: 'Enable server mode (multi-user)' }), + PGADMIN_DISABLE_POSTFIX: bool({ default: true, desc: 'Disable postfix for email' }), + PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: bool({ default: true, desc: 'Enhanced cookie protection' }), + + // Security + PGADMIN_MASTER_PASSWORD_REQUIRED: bool({ default: false, desc: 'Require master password' }), + PGADMIN_SESSION_TIMEOUT: str({ default: '60', desc: 'Session timeout in minutes' }), +}); + +/** + * Mongo Express configuration with validation and defaults + */ +export const mongoExpressConfig = cleanEnv(process.env, { + // Mongo Express Server + MONGO_EXPRESS_HOST: str({ default: 'localhost', desc: 'Mongo Express host' }), + MONGO_EXPRESS_PORT: port({ default: 8081, desc: 'Mongo Express port' }), + + // MongoDB Connection + MONGO_EXPRESS_MONGODB_SERVER: str({ default: 'mongodb', desc: 'MongoDB server name/host' }), + MONGO_EXPRESS_MONGODB_PORT: port({ default: 27017, desc: 'MongoDB port' }), + MONGO_EXPRESS_MONGODB_ADMINUSERNAME: str({ default: 'trading_admin', desc: 'MongoDB admin username' }), + MONGO_EXPRESS_MONGODB_ADMINPASSWORD: str({ default: '', desc: 'MongoDB admin password' }), + + // Basic Authentication for Mongo Express + MONGO_EXPRESS_BASICAUTH_USERNAME: str({ default: 'admin', desc: 'Basic auth username for Mongo Express' }), + MONGO_EXPRESS_BASICAUTH_PASSWORD: str({ default: 'admin123', desc: 'Basic auth password for Mongo Express' }), + + // Configuration + MONGO_EXPRESS_ENABLE_ADMIN: bool({ default: true, desc: 'Enable admin features' }), + MONGO_EXPRESS_OPTIONS_EDITOR_THEME: str({ + default: 'rubyblue', + desc: 'Editor theme (rubyblue, 3024-night, etc.)' + }), + MONGO_EXPRESS_REQUEST_SIZE: str({ default: '100kb', desc: 'Maximum request size' }), +}); + +/** + * Redis Insight configuration with validation and defaults + */ +export const redisInsightConfig = cleanEnv(process.env, { + // Redis Insight Server + REDIS_INSIGHT_HOST: str({ default: 'localhost', desc: 'Redis Insight host' }), + REDIS_INSIGHT_PORT: port({ default: 8001, desc: 'Redis Insight port' }), + + // Redis Connection Settings + REDIS_INSIGHT_REDIS_HOSTS: str({ + default: 'local:dragonfly:6379', + desc: 'Redis hosts in format name:host:port,name:host:port' + }), + + // Configuration + REDIS_INSIGHT_LOG_LEVEL: str({ + default: 'info', + choices: ['error', 'warn', 'info', 'verbose', 'debug'], + desc: 'Redis Insight log level' + }), + REDIS_INSIGHT_DISABLE_ANALYTICS: bool({ default: true, desc: 'Disable analytics collection' }), + REDIS_INSIGHT_BUILD_TYPE: str({ default: 'DOCKER', desc: 'Build type identifier' }), +}); + +// Export typed configuration objects +export type PgAdminConfig = typeof pgAdminConfig; +export type MongoExpressConfig = typeof mongoExpressConfig; +export type RedisInsightConfig = typeof redisInsightConfig; + +// Export individual config values for convenience +export const { + PGADMIN_HOST, + PGADMIN_PORT, + PGADMIN_DEFAULT_EMAIL, + PGADMIN_DEFAULT_PASSWORD, + PGADMIN_SERVER_MODE, + PGADMIN_DISABLE_POSTFIX, + PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION, + PGADMIN_MASTER_PASSWORD_REQUIRED, + PGADMIN_SESSION_TIMEOUT, +} = pgAdminConfig; + +export const { + MONGO_EXPRESS_HOST, + MONGO_EXPRESS_PORT, + MONGO_EXPRESS_MONGODB_SERVER, + MONGO_EXPRESS_MONGODB_PORT, + MONGO_EXPRESS_MONGODB_ADMINUSERNAME, + MONGO_EXPRESS_MONGODB_ADMINPASSWORD, + MONGO_EXPRESS_BASICAUTH_USERNAME, + MONGO_EXPRESS_BASICAUTH_PASSWORD, + MONGO_EXPRESS_ENABLE_ADMIN, + MONGO_EXPRESS_OPTIONS_EDITOR_THEME, + MONGO_EXPRESS_REQUEST_SIZE, +} = mongoExpressConfig; + +export const { + REDIS_INSIGHT_HOST, + REDIS_INSIGHT_PORT, + REDIS_INSIGHT_REDIS_HOSTS, + REDIS_INSIGHT_LOG_LEVEL, + REDIS_INSIGHT_DISABLE_ANALYTICS, + REDIS_INSIGHT_BUILD_TYPE, +} = redisInsightConfig; diff --git a/libs/config/src/core.test.ts b/libs/config/src/core.test.ts deleted file mode 100644 index 74698b8..0000000 --- a/libs/config/src/core.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Tests for the configuration library - */ -import { describe, expect, test, beforeAll, afterAll } from 'bun:test'; -import { - getEnvironment, - validateConfig, - ConfigurationError, - loadEnvVariables, - getEnvVar, - getNumericEnvVar, - getBooleanEnvVar -} from './core'; - -import { Environment, databaseConfigSchema } from './types'; - -describe('Core configuration', () => { - // Save original environment variables - const originalEnv = { ...process.env }; - - // Setup test environment variables - beforeAll(() => { - process.env.NODE_ENV = 'testing'; - process.env.TEST_STRING = 'test-value'; - process.env.TEST_NUMBER = '42'; - process.env.TEST_BOOL_TRUE = 'true'; - process.env.TEST_BOOL_FALSE = 'false'; - }); - - // Restore original environment variables - afterAll(() => { - process.env = { ...originalEnv }; - }); - - test('getEnvironment returns correct environment', () => { - expect(getEnvironment()).toBe(Environment.Testing); - - // Test different environments - process.env.NODE_ENV = 'development'; - expect(getEnvironment()).toBe(Environment.Development); - - process.env.NODE_ENV = 'production'; - expect(getEnvironment()).toBe(Environment.Production); - - process.env.NODE_ENV = 'staging'; - expect(getEnvironment()).toBe(Environment.Staging); - - // Test default environment - process.env.NODE_ENV = 'unknown'; - expect(getEnvironment()).toBe(Environment.Development); - }); - - test('getEnvVar retrieves environment variables', () => { - expect(getEnvVar('TEST_STRING')).toBe('test-value'); - expect(getEnvVar('NON_EXISTENT')).toBeUndefined(); - expect(getEnvVar('NON_EXISTENT', false)).toBeUndefined(); - - // Test required variables - expect(() => getEnvVar('NON_EXISTENT', true)).toThrow(ConfigurationError); - }); - - test('getNumericEnvVar converts to number', () => { - expect(getNumericEnvVar('TEST_NUMBER')).toBe(42); - expect(getNumericEnvVar('NON_EXISTENT', 100)).toBe(100); - - // Test invalid number - process.env.INVALID_NUMBER = 'not-a-number'; - expect(() => getNumericEnvVar('INVALID_NUMBER')).toThrow(ConfigurationError); - }); - - test('getBooleanEnvVar converts to boolean', () => { - expect(getBooleanEnvVar('TEST_BOOL_TRUE')).toBe(true); - expect(getBooleanEnvVar('TEST_BOOL_FALSE')).toBe(false); - expect(getBooleanEnvVar('NON_EXISTENT', true)).toBe(true); - }); - test('validateConfig validates against schema', () => { - // Valid config - const validConfig = { - dragonfly: { - host: 'localhost', - port: 6379, - maxRetriesPerRequest: 3 - }, - questDB: { - host: 'localhost', - port: 8812, - database: 'stockbot', - user: 'admin', - httpPort: 9000 - }, - mongodb: { - uri: 'mongodb://localhost:27017', - database: 'stockbot' - }, - postgres: { - host: 'localhost', - port: 5432, - database: 'stockbot', - user: 'postgres', - poolSize: 10, - ssl: false - } - }; - - expect(() => validateConfig(validConfig, databaseConfigSchema)).not.toThrow(); - // Invalid config (missing required field) - const invalidConfig = { - dragonfly: { - host: 'localhost', - // missing port - maxRetriesPerRequest: 3 - }, - questDB: { - host: 'localhost', - port: 8812, - database: 'stockbot', - user: 'admin', - httpPort: 9000 - }, - mongodb: { - uri: 'mongodb://localhost:27017', - database: 'stockbot' - }, - postgres: { - host: 'localhost', - port: 5432, - database: 'stockbot', - user: 'postgres', - poolSize: 10, - ssl: false - } - }; - - expect(() => validateConfig(invalidConfig, databaseConfigSchema)).toThrow(ConfigurationError); - }); -}); diff --git a/libs/config/src/core.ts b/libs/config/src/core.ts index 924215e..d49beea 100644 --- a/libs/config/src/core.ts +++ b/libs/config/src/core.ts @@ -1,10 +1,8 @@ /** - * Core configuration module for the Stock Bot platform + * Core configuration module for the Stock Bot platform using envalid */ import { config as dotenvConfig } from 'dotenv'; import path from 'node:path'; -import { z } from 'zod'; -import { Environment } from './types'; /** * Represents an error related to configuration validation @@ -16,6 +14,16 @@ export class ConfigurationError extends Error { } } +/** + * Environment types + */ +export enum Environment { + Development = 'development', + Testing = 'testing', + Staging = 'staging', + Production = 'production' +} + /** * Loads environment variables from .env files based on the current environment */ @@ -57,106 +65,3 @@ export function getEnvironment(): Environment { return Environment.Development; } } - -/** - * Validates configuration using Zod schema - */ -export function validateConfig(config: unknown, schema: z.ZodSchema): T { - try { - return schema.parse(config); - } catch (error) { - if (error instanceof z.ZodError) { - const issues = error.issues.map(issue => - `${issue.path.join('.')}: ${issue.message}` - ).join('\n'); - - throw new ConfigurationError(`Configuration validation failed:\n${issues}`); - } - throw new ConfigurationError('Invalid configuration'); - } -} - -/** - * Retrieves an environment variable with validation - */ -export function getEnvVar(key: string, required: boolean = false): string | undefined { - const value = process.env[key]; - - if (required && (value === undefined || value === '')) { - throw new ConfigurationError(`Required environment variable ${key} is missing`); - } - - return value; -} - -/** - * Retrieves a numeric environment variable with validation - */ -export function getNumericEnvVar(key: string, defaultValue?: number): number { - const value = process.env[key]; - if (value === undefined || value === '') { - if (defaultValue !== undefined) { - return defaultValue; - } - throw new ConfigurationError(`Required numeric environment variable ${key} is missing`); - } - - const numValue = Number(value); - - if (isNaN(numValue)) { - throw new ConfigurationError(`Environment variable ${key} is not a valid number`); - } - - return numValue; -} - -/** - * Retrieves a boolean environment variable with validation - */ -export function getBooleanEnvVar(key: string, defaultValue?: boolean): boolean { - const value = process.env[key]; - if (value === undefined || value === '') { - if (defaultValue !== undefined) { - return defaultValue; - } - throw new ConfigurationError(`Required boolean environment variable ${key} is missing`); - } - - return value.toLowerCase() === 'true' || value === '1'; -} - -/** - * Creates a typed dynamic configuration loader for a specific service - */ -export function createConfigLoader( - serviceName: string, - schema: z.ZodSchema, - defaultConfig: Partial = {} -): () => T { - return (): T => { - try { - loadEnvVariables(); - const configEnvVar = `${serviceName.toUpperCase()}_CONFIG`; - let config = { ...defaultConfig } as unknown as T; - - // Try to load JSON from environment variable if available - const configJson = process.env[configEnvVar]; - if (configJson) { - try { - const parsedConfig = JSON.parse(configJson); - config = { ...config, ...parsedConfig }; - } catch (error) { - throw new ConfigurationError(`Invalid JSON in ${configEnvVar} environment variable`); - } - } - - // Validate and return the config - return validateConfig(config, schema); - } catch (error) { - if (error instanceof ConfigurationError) { - throw error; - } - throw new ConfigurationError(`Failed to load configuration for service ${serviceName}: ${error}`); - } - }; -} diff --git a/libs/config/src/data-providers.ts b/libs/config/src/data-providers.ts index 26df731..417b0be 100644 --- a/libs/config/src/data-providers.ts +++ b/libs/config/src/data-providers.ts @@ -1,85 +1,157 @@ /** - * Data provider configurations for market data + * Data provider configurations using envalid */ -import { getEnvVar, validateConfig, createConfigLoader } from './core'; -import { dataProvidersConfigSchema, DataProvidersConfig, DataProviderConfig } from './types'; +import { cleanEnv, str, num, bool } from 'envalid'; /** - * Default data provider configurations + * Data providers configuration with validation and defaults */ -const defaultDataProviders: DataProviderConfig[] = [ - { - name: 'alpaca', - type: 'rest', - baseUrl: 'https://data.alpaca.markets/v1beta1', - apiKey: '', - apiSecret: '', - rateLimits: { - maxRequestsPerMinute: 200 - } - }, - { - name: 'polygon', - type: 'rest', - baseUrl: 'https://api.polygon.io/v2', - apiKey: '', - rateLimits: { - maxRequestsPerMinute: 5 - } - }, - { - name: 'alpaca-websocket', - type: 'websocket', - wsUrl: 'wss://stream.data.alpaca.markets/v2/iex', - apiKey: '', - apiSecret: '' +export const dataProvidersConfig = cleanEnv(process.env, { + // Default Provider + DEFAULT_DATA_PROVIDER: str({ + choices: ['alpaca', 'polygon', 'yahoo', 'iex'], + default: 'alpaca', + desc: 'Default data provider' + }), + + // Alpaca Configuration + ALPACA_API_KEY: str({ default: '', desc: 'Alpaca API key' }), + ALPACA_API_SECRET: str({ default: '', desc: 'Alpaca API secret' }), + ALPACA_BASE_URL: str({ default: 'https://data.alpaca.markets/v1beta1', desc: 'Alpaca base URL' }), + ALPACA_RATE_LIMIT: num({ default: 200, desc: 'Alpaca rate limit per minute' }), + ALPACA_ENABLED: bool({ default: true, desc: 'Enable Alpaca provider' }), + + // Polygon Configuration + POLYGON_API_KEY: str({ default: '', desc: 'Polygon API key' }), + POLYGON_BASE_URL: str({ default: 'https://api.polygon.io', desc: 'Polygon base URL' }), + POLYGON_RATE_LIMIT: num({ default: 5, desc: 'Polygon rate limit per minute' }), + POLYGON_ENABLED: bool({ default: false, desc: 'Enable Polygon provider' }), + + // Yahoo Finance Configuration + YAHOO_BASE_URL: str({ default: 'https://query1.finance.yahoo.com', desc: 'Yahoo Finance base URL' }), + YAHOO_RATE_LIMIT: num({ default: 2000, desc: 'Yahoo Finance rate limit per hour' }), + YAHOO_ENABLED: bool({ default: true, desc: 'Enable Yahoo Finance provider' }), + + // IEX Cloud Configuration + IEX_API_KEY: str({ default: '', desc: 'IEX Cloud API key' }), + IEX_BASE_URL: str({ default: 'https://cloud.iexapis.com/stable', desc: 'IEX Cloud base URL' }), + IEX_RATE_LIMIT: num({ default: 100, desc: 'IEX Cloud rate limit per second' }), + IEX_ENABLED: bool({ default: false, desc: 'Enable IEX Cloud provider' }), + + // Connection Settings + DATA_PROVIDER_TIMEOUT: num({ default: 30000, desc: 'Request timeout in milliseconds' }), + DATA_PROVIDER_RETRIES: num({ default: 3, desc: 'Number of retry attempts' }), + DATA_PROVIDER_RETRY_DELAY: num({ default: 1000, desc: 'Retry delay in milliseconds' }), + + // Cache Settings + DATA_CACHE_ENABLED: bool({ default: true, desc: 'Enable data caching' }), + DATA_CACHE_TTL: num({ default: 300000, desc: 'Cache TTL in milliseconds' }), + DATA_CACHE_MAX_SIZE: num({ default: 1000, desc: 'Maximum cache entries' }), +}); + +/** + * Helper function to get provider-specific configuration + */ +export function getProviderConfig(providerName: string) { + const name = providerName.toUpperCase(); + + switch (name) { + case 'ALPACA': + return { + name: 'alpaca', + type: 'rest' as const, + enabled: dataProvidersConfig.ALPACA_ENABLED, + baseUrl: dataProvidersConfig.ALPACA_BASE_URL, + apiKey: dataProvidersConfig.ALPACA_API_KEY, + apiSecret: dataProvidersConfig.ALPACA_API_SECRET, + rateLimits: { + maxRequestsPerMinute: dataProvidersConfig.ALPACA_RATE_LIMIT + } + }; + + case 'POLYGON': + return { + name: 'polygon', + type: 'rest' as const, + enabled: dataProvidersConfig.POLYGON_ENABLED, + baseUrl: dataProvidersConfig.POLYGON_BASE_URL, + apiKey: dataProvidersConfig.POLYGON_API_KEY, + rateLimits: { + maxRequestsPerMinute: dataProvidersConfig.POLYGON_RATE_LIMIT + } + }; + + case 'YAHOO': + return { + name: 'yahoo', + type: 'rest' as const, + enabled: dataProvidersConfig.YAHOO_ENABLED, + baseUrl: dataProvidersConfig.YAHOO_BASE_URL, + rateLimits: { + maxRequestsPerHour: dataProvidersConfig.YAHOO_RATE_LIMIT + } + }; + + case 'IEX': + return { + name: 'iex', + type: 'rest' as const, + enabled: dataProvidersConfig.IEX_ENABLED, + baseUrl: dataProvidersConfig.IEX_BASE_URL, + apiKey: dataProvidersConfig.IEX_API_KEY, + rateLimits: { + maxRequestsPerSecond: dataProvidersConfig.IEX_RATE_LIMIT + } + }; + + default: + throw new Error(`Unknown provider: ${providerName}`); } -]; - -/** - * Load data provider configurations from environment variables - */ -export function loadDataProviderConfigs(): DataProvidersConfig { - // Get provider specific environment variables - const providers = defaultDataProviders.map(provider => { - const nameUpper = provider.name.toUpperCase().replace('-', '_'); - - const updatedProvider: DataProviderConfig = { - ...provider, - apiKey: getEnvVar(`${nameUpper}_API_KEY`) || provider.apiKey || '', - }; - - if (provider.apiSecret !== undefined) { - updatedProvider.apiSecret = getEnvVar(`${nameUpper}_API_SECRET`) || provider.apiSecret || ''; - } - - return updatedProvider; - }); - - // Load default provider from environment - const defaultProvider = getEnvVar('DEFAULT_DATA_PROVIDER') || 'alpaca'; - - const config: DataProvidersConfig = { - providers, - defaultProvider - }; - - return validateConfig(config, dataProvidersConfigSchema); } /** - * Creates a dynamic configuration loader for data providers + * Get all enabled providers */ -export const createDataProvidersConfig = createConfigLoader( - 'data-providers', - dataProvidersConfigSchema, - { - providers: defaultDataProviders, - defaultProvider: 'alpaca' - } -); +export function getEnabledProviders() { + const providers = ['alpaca', 'polygon', 'yahoo', 'iex']; + return providers + .map(provider => getProviderConfig(provider)) + .filter(config => config.enabled); +} /** - * Singleton data provider configurations + * Get the default provider configuration */ -export const dataProviderConfigs = loadDataProviderConfigs(); +export function getDefaultProvider() { + return getProviderConfig(dataProvidersConfig.DEFAULT_DATA_PROVIDER); +} + +// Export typed configuration object +export type DataProvidersConfig = typeof dataProvidersConfig; + +// Export individual config values for convenience +export const { + DEFAULT_DATA_PROVIDER, + ALPACA_API_KEY, + ALPACA_API_SECRET, + ALPACA_BASE_URL, + ALPACA_RATE_LIMIT, + ALPACA_ENABLED, + POLYGON_API_KEY, + POLYGON_BASE_URL, + POLYGON_RATE_LIMIT, + POLYGON_ENABLED, + YAHOO_BASE_URL, + YAHOO_RATE_LIMIT, + YAHOO_ENABLED, + IEX_API_KEY, + IEX_BASE_URL, + IEX_RATE_LIMIT, + IEX_ENABLED, + DATA_PROVIDER_TIMEOUT, + DATA_PROVIDER_RETRIES, + DATA_PROVIDER_RETRY_DELAY, + DATA_CACHE_ENABLED, + DATA_CACHE_TTL, + DATA_CACHE_MAX_SIZE, +} = dataProvidersConfig; diff --git a/libs/config/src/database.ts b/libs/config/src/database.ts index cba2e4d..617d35c 100644 --- a/libs/config/src/database.ts +++ b/libs/config/src/database.ts @@ -1,91 +1,54 @@ /** - * Database configuration for Stock Bot services + * Database configuration using envalid */ -import { z } from 'zod'; -import { getEnvVar, getNumericEnvVar, validateConfig, createConfigLoader } from './core'; -import { databaseConfigSchema, DatabaseConfig } from './types'; +import { cleanEnv, str, port, bool, num } from 'envalid'; /** - * Default database configuration + * Database configuration with validation and defaults */ -const defaultDatabaseConfig: DatabaseConfig = { - dragonfly: { - host: 'localhost', - port: 6379, - maxRetriesPerRequest: 3 - }, - questDB: { - host: 'localhost', - port: 8812, - database: 'stockbot', - user: 'admin', - httpPort: 9000 - }, - mongodb: { - uri: 'mongodb://localhost:27017', - database: 'stockbot' - }, - postgres: { - host: 'localhost', - port: 5432, - database: 'stockbot', - user: 'postgres', - poolSize: 10, - ssl: false - } -}; +export const databaseConfig = cleanEnv(process.env, { + // PostgreSQL Configuration + DB_HOST: str({ default: 'localhost', desc: 'Database host' }), + DB_PORT: port({ default: 5432, desc: 'Database port' }), + DB_NAME: str({ default: 'stockbot', desc: 'Database name' }), + DB_USER: str({ default: 'stockbot', desc: 'Database user' }), + DB_PASSWORD: str({ default: '', desc: 'Database password' }), + + // Connection Pool Settings + DB_POOL_MIN: num({ default: 2, desc: 'Minimum pool connections' }), + DB_POOL_MAX: num({ default: 10, desc: 'Maximum pool connections' }), + DB_POOL_IDLE_TIMEOUT: num({ default: 30000, desc: 'Pool idle timeout in ms' }), + + // SSL Configuration + DB_SSL: bool({ default: false, desc: 'Enable SSL for database connection' }), + DB_SSL_REJECT_UNAUTHORIZED: bool({ default: true, desc: 'Reject unauthorized SSL certificates' }), + + // Additional Settings + DB_QUERY_TIMEOUT: num({ default: 30000, desc: 'Query timeout in ms' }), + DB_CONNECTION_TIMEOUT: num({ default: 5000, desc: 'Connection timeout in ms' }), + DB_STATEMENT_TIMEOUT: num({ default: 30000, desc: 'Statement timeout in ms' }), + DB_LOCK_TIMEOUT: num({ default: 10000, desc: 'Lock timeout in ms' }), + DB_IDLE_IN_TRANSACTION_SESSION_TIMEOUT: num({ default: 60000, desc: 'Idle in transaction timeout in ms' }), +}); -/** - * Load database configuration from environment variables - */ -export function loadDatabaseConfig(): DatabaseConfig { - const config = { - dragonfly: { - host: getEnvVar('DRAGONFLY_HOST') || defaultDatabaseConfig.dragonfly.host, - port: getNumericEnvVar('DRAGONFLY_PORT', defaultDatabaseConfig.dragonfly.port), - password: getEnvVar('DRAGONFLY_PASSWORD'), - maxRetriesPerRequest: getNumericEnvVar('DRAGONFLY_MAX_RETRIES_PER_REQUEST', - defaultDatabaseConfig.dragonfly.maxRetriesPerRequest) - }, - questDB: { - host: getEnvVar('QUESTDB_HOST') || defaultDatabaseConfig.questDB.host, - port: getNumericEnvVar('QUESTDB_PORT', defaultDatabaseConfig.questDB.port), - database: getEnvVar('QUESTDB_DB') || defaultDatabaseConfig.questDB.database, - user: getEnvVar('QUESTDB_USER') || defaultDatabaseConfig.questDB.user, - password: getEnvVar('QUESTDB_PASSWORD'), - httpPort: getNumericEnvVar('QUESTDB_HTTP_PORT', defaultDatabaseConfig.questDB.httpPort) - }, - mongodb: { - uri: getEnvVar('MONGODB_URI') || defaultDatabaseConfig.mongodb.uri, - database: getEnvVar('MONGODB_DATABASE') || defaultDatabaseConfig.mongodb.database, - username: getEnvVar('MONGODB_USERNAME'), - password: getEnvVar('MONGODB_PASSWORD'), - options: process.env.MONGODB_OPTIONS ? JSON.parse(process.env.MONGODB_OPTIONS) : undefined - }, - postgres: { - host: getEnvVar('POSTGRES_HOST') || defaultDatabaseConfig.postgres.host, - port: getNumericEnvVar('POSTGRES_PORT', defaultDatabaseConfig.postgres.port), - database: getEnvVar('POSTGRES_DB') || defaultDatabaseConfig.postgres.database, - user: getEnvVar('POSTGRES_USER') || defaultDatabaseConfig.postgres.user, - password: getEnvVar('POSTGRES_PASSWORD'), - ssl: process.env.POSTGRES_SSL === 'true', - poolSize: getNumericEnvVar('POSTGRES_POOL_SIZE', defaultDatabaseConfig.postgres.poolSize) - } - }; +// Export typed configuration object +export type DatabaseConfig = typeof databaseConfig; - return validateConfig(config, databaseConfigSchema); -} - -/** - * Creates a dynamic configuration loader for database config - */ -export const createDatabaseConfig = createConfigLoader( - 'database', - databaseConfigSchema, - defaultDatabaseConfig -); - -/** - * Singleton database configuration - */ -export const databaseConfig = loadDatabaseConfig(); +// Export individual config values for convenience +export const { + DB_HOST, + DB_PORT, + DB_NAME, + DB_USER, + DB_PASSWORD, + DB_POOL_MIN, + DB_POOL_MAX, + DB_POOL_IDLE_TIMEOUT, + DB_SSL, + DB_SSL_REJECT_UNAUTHORIZED, + DB_QUERY_TIMEOUT, + DB_CONNECTION_TIMEOUT, + DB_STATEMENT_TIMEOUT, + DB_LOCK_TIMEOUT, + DB_IDLE_IN_TRANSACTION_SESSION_TIMEOUT, +} = databaseConfig; diff --git a/libs/config/src/dragonfly.ts b/libs/config/src/dragonfly.ts new file mode 100644 index 0000000..117be83 --- /dev/null +++ b/libs/config/src/dragonfly.ts @@ -0,0 +1,79 @@ +/** + * Dragonfly (Redis replacement) configuration using envalid + * High-performance caching and event streaming + */ +import { cleanEnv, str, port, bool, num } from 'envalid'; + +/** + * Dragonfly configuration with validation and defaults + */ +export const dragonflyConfig = cleanEnv(process.env, { + // Dragonfly Connection + DRAGONFLY_HOST: str({ default: 'localhost', desc: 'Dragonfly host' }), + DRAGONFLY_PORT: port({ default: 6379, desc: 'Dragonfly port' }), + DRAGONFLY_PASSWORD: str({ default: '', desc: 'Dragonfly password (if auth enabled)' }), + DRAGONFLY_USERNAME: str({ default: '', desc: 'Dragonfly username (if ACL enabled)' }), + + // Database Selection + DRAGONFLY_DATABASE: num({ default: 0, desc: 'Dragonfly database number (0-15)' }), + + // Connection Pool Settings + DRAGONFLY_MAX_RETRIES: num({ default: 3, desc: 'Maximum retry attempts' }), + DRAGONFLY_RETRY_DELAY: num({ default: 50, desc: 'Retry delay in ms' }), + DRAGONFLY_CONNECT_TIMEOUT: num({ default: 10000, desc: 'Connection timeout in ms' }), + DRAGONFLY_COMMAND_TIMEOUT: num({ default: 5000, desc: 'Command timeout in ms' }), + + // Pool Configuration + DRAGONFLY_POOL_SIZE: num({ default: 10, desc: 'Connection pool size' }), + DRAGONFLY_POOL_MIN: num({ default: 1, desc: 'Minimum pool connections' }), + DRAGONFLY_POOL_MAX: num({ default: 20, desc: 'Maximum pool connections' }), + + // TLS Settings + DRAGONFLY_TLS: bool({ default: false, desc: 'Enable TLS for Dragonfly connection' }), + DRAGONFLY_TLS_CERT_FILE: str({ default: '', desc: 'Path to TLS certificate file' }), + DRAGONFLY_TLS_KEY_FILE: str({ default: '', desc: 'Path to TLS key file' }), + DRAGONFLY_TLS_CA_FILE: str({ default: '', desc: 'Path to TLS CA certificate file' }), + DRAGONFLY_TLS_SKIP_VERIFY: bool({ default: false, desc: 'Skip TLS certificate verification' }), + + // Performance Settings + DRAGONFLY_ENABLE_KEEPALIVE: bool({ default: true, desc: 'Enable TCP keepalive' }), + DRAGONFLY_KEEPALIVE_INTERVAL: num({ default: 60, desc: 'Keepalive interval in seconds' }), + + // Clustering (if using cluster mode) + DRAGONFLY_CLUSTER_MODE: bool({ default: false, desc: 'Enable cluster mode' }), + DRAGONFLY_CLUSTER_NODES: str({ default: '', desc: 'Comma-separated list of cluster nodes (host:port)' }), + + // Memory and Cache Settings + DRAGONFLY_MAX_MEMORY: str({ default: '2gb', desc: 'Maximum memory usage' }), + DRAGONFLY_CACHE_MODE: bool({ default: true, desc: 'Enable cache mode' }), +}); + +// Export typed configuration object +export type DragonflyConfig = typeof dragonflyConfig; + +// Export individual config values for convenience +export const { + DRAGONFLY_HOST, + DRAGONFLY_PORT, + DRAGONFLY_PASSWORD, + DRAGONFLY_USERNAME, + DRAGONFLY_DATABASE, + DRAGONFLY_MAX_RETRIES, + DRAGONFLY_RETRY_DELAY, + DRAGONFLY_CONNECT_TIMEOUT, + DRAGONFLY_COMMAND_TIMEOUT, + DRAGONFLY_POOL_SIZE, + DRAGONFLY_POOL_MIN, + DRAGONFLY_POOL_MAX, + DRAGONFLY_TLS, + DRAGONFLY_TLS_CERT_FILE, + DRAGONFLY_TLS_KEY_FILE, + DRAGONFLY_TLS_CA_FILE, + DRAGONFLY_TLS_SKIP_VERIFY, + DRAGONFLY_ENABLE_KEEPALIVE, + DRAGONFLY_KEEPALIVE_INTERVAL, + DRAGONFLY_CLUSTER_MODE, + DRAGONFLY_CLUSTER_NODES, + DRAGONFLY_MAX_MEMORY, + DRAGONFLY_CACHE_MODE, +} = dragonflyConfig; diff --git a/libs/config/src/example.ts b/libs/config/src/example.ts index 05054ae..3cea074 100644 --- a/libs/config/src/example.ts +++ b/libs/config/src/example.ts @@ -1,81 +1,818 @@ /** - * Example usage of the @stock-bot/config library + * Example usage of the Stock Bot configuration library + * + * This file demonstrates how to use the envalid-based configuration + * system for various services in the Stock Bot platform. */ + +// Import all the configuration modules import { - databaseConfig, - dataProviderConfigs, - riskConfig, - Environment, + // Core utilities + loadEnvVariables, getEnvironment, - marketDataGatewayConfig, - riskGuardianConfig, + Environment, ConfigurationError, - validateConfig -} from './index'; +} from './core'; + +import { + // Database configuration + databaseConfig, + DatabaseConfig, + DB_HOST, + DB_PORT, + DB_NAME, + DB_USER, + DB_PASSWORD, +} from './database'; + +import { + // QuestDB configuration + questdbConfig, + QuestDbConfig, + QUESTDB_HOST, + QUESTDB_HTTP_PORT, + QUESTDB_PG_PORT, +} from './questdb'; + +import { + // MongoDB configuration + mongodbConfig, + MongoDbConfig, + MONGODB_HOST, + MONGODB_PORT, + MONGODB_DATABASE, + MONGODB_USERNAME, +} from './mongodb'; + +import { + // Dragonfly configuration + dragonflyConfig, + DragonflyConfig, + DRAGONFLY_HOST, + DRAGONFLY_PORT, + DRAGONFLY_DATABASE, +} from './dragonfly'; + +import { + // Monitoring configuration + prometheusConfig, + grafanaConfig, + PrometheusConfig, + GrafanaConfig, + PROMETHEUS_HOST, + PROMETHEUS_PORT, + GRAFANA_HOST, + GRAFANA_PORT, +} from './monitoring'; + +import { + // Loki configuration + lokiConfig, + LokiConfig, + LOKI_HOST, + LOKI_PORT, + LOKI_SERVICE_LABEL, + LOKI_BATCH_SIZE, +} from './loki'; + +import { + // Logging configuration + loggingConfig, + LoggingConfig, + LOG_LEVEL, + LOG_FORMAT, + LOG_CONSOLE, + LOG_FILE, + LOG_SERVICE_NAME, +} from './logging'; + + +import { + // Risk management configuration + riskConfig, + RiskConfig, + RISK_MAX_POSITION_SIZE, + RISK_DEFAULT_STOP_LOSS, + RISK_CIRCUIT_BREAKER_ENABLED, +} from './risk'; + +import { + // Data provider configuration + dataProvidersConfig, + DataProvidersConfig, + getProviderConfig, + getEnabledProviders, + getDefaultProvider, + DEFAULT_DATA_PROVIDER, + ALPACA_API_KEY, +} from './data-providers'; /** - * Display current configuration values + * Example 1: Basic usage - Load environment variables and get configuration */ -export function printCurrentConfig(): void { - console.log('\n=== Stock Bot Configuration ==='); +function basicUsageExample() { + console.log('=== Basic Usage Example ==='); - console.log('\nEnvironment:', getEnvironment()); - console.log('\n--- Database Config ---'); - console.log('Dragonfly Host:', databaseConfig.dragonfly.host); - console.log('Dragonfly Port:', databaseConfig.dragonfly.port); - console.log('QuestDB Host:', databaseConfig.questDB.host); - console.log('QuestDB Database:', databaseConfig.questDB.database); - console.log('MongoDB URI:', databaseConfig.mongodb.uri); - console.log('MongoDB Database:', databaseConfig.mongodb.database); - console.log('PostgreSQL Host:', databaseConfig.postgres.host); - console.log('PostgreSQL Database:', databaseConfig.postgres.database); - console.log('\n--- Data Provider Config ---'); - console.log('Default Provider:', dataProviderConfigs.defaultProvider); - console.log('Providers:'); - dataProviderConfigs.providers.forEach((provider: { - name: string; - type: string; - baseUrl?: string; - wsUrl?: string; - }) => { - console.log(` - ${provider.name} (${provider.type})`); - if (provider.baseUrl) console.log(` URL: ${provider.baseUrl}`); - if (provider.wsUrl) console.log(` WebSocket: ${provider.wsUrl}`); + // Load environment variables (optional - they're loaded automatically) + loadEnvVariables(); + + // Get the current environment + const env = getEnvironment(); + console.log(`Current environment: ${env}`); + + // Access individual configuration values + console.log(`Database host: ${DB_HOST}`); + console.log(`Database port: ${DB_PORT}`); + console.log(`Log level: ${LOG_LEVEL}`); + + // Access full configuration objects + console.log(`Full database config:`, { + host: databaseConfig.DB_HOST, + port: databaseConfig.DB_PORT, + name: databaseConfig.DB_NAME, + ssl: databaseConfig.DB_SSL, }); - - console.log('\n--- Risk Config ---'); - console.log('Max Drawdown:', riskConfig.maxDrawdown * 100, '%'); - console.log('Max Position Size:', riskConfig.maxPositionSize * 100, '%'); - console.log('Max Leverage:', riskConfig.maxLeverage, 'x'); - console.log('Default Stop Loss:', riskConfig.stopLossDefault * 100, '%'); - console.log('Default Take Profit:', riskConfig.takeProfitDefault * 100, '%'); - - console.log('\n--- Market Data Gateway Config ---'); - console.log('Service Port:', marketDataGatewayConfig.service.port); - console.log('WebSocket Enabled:', marketDataGatewayConfig.websocket.enabled); - console.log('WebSocket Path:', marketDataGatewayConfig.websocket.path); - console.log('Caching Enabled:', marketDataGatewayConfig.caching.enabled); - console.log('Caching TTL:', marketDataGatewayConfig.caching.ttlSeconds, 'seconds'); - - console.log('\n--- Risk Guardian Config ---'); - console.log('Service Port:', riskGuardianConfig.service.port); - console.log('Pre-Trade Validation:', riskGuardianConfig.riskChecks.preTradeValidation); - console.log('Portfolio Validation:', riskGuardianConfig.riskChecks.portfolioValidation); - console.log('Alerting Enabled:', riskGuardianConfig.alerting.enabled); - console.log('Critical Threshold:', riskGuardianConfig.alerting.criticalThreshold * 100, '%'); } -// Execute example if this file is run directly -if (require.main === module) { try { - printCurrentConfig(); - } catch (error: unknown) { - if (error instanceof ConfigurationError) { - console.error('Configuration Error:', error.message); - } else if (error instanceof Error) { - console.error('Error:', error.message); - } else { - console.error('Unknown error:', error); - } - process.exit(1); +/** + * Example 2: Using configuration in a database connection + */ +async function databaseConnectionExample() { + console.log('=== Database Connection Example ==='); + + try { + // Use the database configuration to create a connection string + const connectionString = `postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}`; + + console.log('Database connection settings:'); + console.log(`- Host: ${databaseConfig.DB_HOST}`); + console.log(`- Port: ${databaseConfig.DB_PORT}`); + console.log(`- Database: ${databaseConfig.DB_NAME}`); + console.log(`- SSL enabled: ${databaseConfig.DB_SSL}`); + console.log(`- Pool max connections: ${databaseConfig.DB_POOL_MAX}`); + console.log(`- Query timeout: ${databaseConfig.DB_QUERY_TIMEOUT}ms`); + + // Example pool configuration + const poolConfig = { + host: databaseConfig.DB_HOST, + port: databaseConfig.DB_PORT, + database: databaseConfig.DB_NAME, + user: databaseConfig.DB_USER, + password: databaseConfig.DB_PASSWORD, + ssl: databaseConfig.DB_SSL, + min: databaseConfig.DB_POOL_MIN, + max: databaseConfig.DB_POOL_MAX, + idleTimeoutMillis: databaseConfig.DB_POOL_IDLE_TIMEOUT, + }; + + console.log('Pool configuration:', poolConfig); + + } catch (error) { + console.error('Database configuration error:', error); } } + + +/** + * Example 3: Logging setup example + */ +function loggingSetupExample() { + console.log('=== Logging Setup Example ==='); + + // Access logging configuration + console.log('Logging settings:'); + console.log(`- Level: ${loggingConfig.LOG_LEVEL}`); + console.log(`- Format: ${loggingConfig.LOG_FORMAT}`); + console.log(`- Console enabled: ${loggingConfig.LOG_CONSOLE}`); + console.log(`- File logging: ${loggingConfig.LOG_FILE}`); + console.log(`- Service name: ${loggingConfig.LOG_SERVICE_NAME}`); + // Example logger configuration + const loggerConfig = { + level: loggingConfig.LOG_LEVEL, + format: loggingConfig.LOG_FORMAT, + transports: [] as any[], + defaultMeta: { + service: loggingConfig.LOG_SERVICE_NAME, + version: loggingConfig.LOG_SERVICE_VERSION, + environment: loggingConfig.LOG_ENVIRONMENT, + }, + }; + + if (loggingConfig.LOG_CONSOLE) { + loggerConfig.transports.push({ + type: 'console', + format: loggingConfig.LOG_FORMAT, + timestamp: loggingConfig.LOG_TIMESTAMP, + }); + } + + if (loggingConfig.LOG_FILE) { + loggerConfig.transports.push({ + type: 'file', + filename: `${loggingConfig.LOG_FILE_PATH}/application.log`, + maxSize: loggingConfig.LOG_FILE_MAX_SIZE, + maxFiles: loggingConfig.LOG_FILE_MAX_FILES, + datePattern: loggingConfig.LOG_FILE_DATE_PATTERN, + }); + } + + // Example Loki transport configuration + if (lokiConfig.LOKI_HOST) { + loggerConfig.transports.push({ + type: 'loki', + host: lokiConfig.LOKI_HOST, + port: lokiConfig.LOKI_PORT, + batchSize: lokiConfig.LOKI_BATCH_SIZE, + labels: { + service: lokiConfig.LOKI_SERVICE_LABEL, + environment: lokiConfig.LOKI_ENVIRONMENT_LABEL, + }, + }); + } + console.log('Logger configuration:', loggerConfig); +} + +/** + * Example 4: Risk management configuration + */ +function riskManagementExample() { + console.log('=== Risk Management Example ==='); + + // Access risk configuration + console.log('Risk management settings:'); + console.log(`- Max position size: ${RISK_MAX_POSITION_SIZE * 100}%`); + console.log(`- Default stop loss: ${RISK_DEFAULT_STOP_LOSS * 100}%`); + console.log(`- Circuit breaker enabled: ${RISK_CIRCUIT_BREAKER_ENABLED}`); + console.log(`- Max leverage: ${riskConfig.RISK_MAX_LEVERAGE}x`); + + // Example risk calculator + function calculatePositionSize(portfolioValue: number, riskPerTrade: number = RISK_DEFAULT_STOP_LOSS) { + const maxPositionValue = portfolioValue * RISK_MAX_POSITION_SIZE; + const riskAmount = portfolioValue * riskPerTrade; + + return { + maxPositionValue, + riskAmount, + maxShares: Math.floor(maxPositionValue / 100), // Assuming $100 per share + }; + } + + const portfolioValue = 100000; // $100k portfolio + const position = calculatePositionSize(portfolioValue); + console.log(`Position sizing for $${portfolioValue} portfolio:`, position); +} + +/** + * Example 5: Data provider configuration + */ +function dataProviderExample() { + console.log('=== Data Provider Example ==='); + + // Get the default provider + const defaultProvider = getDefaultProvider(); + console.log('Default provider:', defaultProvider); + + // Get all enabled providers + const enabledProviders = getEnabledProviders(); + console.log('Enabled providers:', enabledProviders.map(p => p.name)); + + // Get specific provider configuration + try { + const alpacaConfig = getProviderConfig('alpaca'); + console.log('Alpaca configuration:', { + enabled: alpacaConfig.enabled, + baseUrl: alpacaConfig.baseUrl, + hasApiKey: !!alpacaConfig.apiKey, + rateLimit: alpacaConfig.rateLimits, + }); + } catch (error) { + console.error('Error getting Alpaca config:', error); + } + + // Example API client setup + const apiClients = enabledProviders.map(provider => ({ + name: provider.name, + client: { + baseURL: provider.baseUrl, + timeout: dataProvidersConfig.DATA_PROVIDER_TIMEOUT, + retries: dataProvidersConfig.DATA_PROVIDER_RETRIES, + retryDelay: dataProvidersConfig.DATA_PROVIDER_RETRY_DELAY, + headers: provider.apiKey ? { + 'Authorization': `Bearer ${provider.apiKey}` + } : {}, + } + })); + + console.log('API clients configuration:', apiClients); +} + +/** + * Example 6: Environment-specific configuration + */ +function environmentSpecificExample() { + console.log('=== Environment-Specific Example ==='); + + const env = getEnvironment(); + + switch (env) { + case Environment.Development: + console.log('Development environment detected'); + console.log('- Using local database'); + console.log('- Verbose logging enabled'); + console.log('- Paper trading mode'); + break; + + case Environment.Testing: + console.log('Testing environment detected'); + console.log('- Using test database'); + console.log('- Structured logging'); + console.log('- Mock data providers'); + break; + + case Environment.Staging: + console.log('Staging environment detected'); + console.log('- Using staging database'); + console.log('- Production-like settings'); + console.log('- Real data providers (limited)'); + break; + + case Environment.Production: + console.log('Production environment detected'); + console.log('- Using production database'); + console.log('- Optimized logging'); + console.log('- Live trading enabled'); + break; + } + + // Example of environment-specific behavior + const isProduction = env === Environment.Production; + const tradingMode = isProduction ? 'live' : 'paper'; + const logLevel = isProduction ? 'info' : 'debug'; + + console.log(`Trading mode: ${tradingMode}`); + console.log(`Recommended log level: ${logLevel}`); +} + +/** + * Example 7: Configuration validation and error handling + */ +function configurationValidationExample() { + console.log('=== Configuration Validation Example ==='); + + try { + // Check required configurations + if (!ALPACA_API_KEY && DEFAULT_DATA_PROVIDER === 'alpaca') { + throw new ConfigurationError('Alpaca API key is required when using Alpaca as default provider'); + } + + // Validate risk settings + if (RISK_MAX_POSITION_SIZE > 1.0) { + throw new ConfigurationError('Maximum position size cannot exceed 100%'); + } + + if (riskConfig.RISK_DEFAULT_STOP_LOSS > riskConfig.RISK_DEFAULT_TAKE_PROFIT) { + console.warn('Warning: Stop loss is greater than take profit - check your risk settings'); + } + + // Validate database connection settings + if (databaseConfig.DB_POOL_MAX < databaseConfig.DB_POOL_MIN) { + throw new ConfigurationError('Database max pool size must be greater than min pool size'); + } + + console.log('✅ All configuration validations passed'); + + } catch (error) { + if (error instanceof ConfigurationError) { + console.error('❌ Configuration error:', error.message); + } else { + console.error('❌ Unexpected error:', error); + } + } +} + +/** + * Example 8: QuestDB time-series database configuration + */ +function questdbConfigurationExample() { + console.log('=== QuestDB Configuration Example ==='); + + // Access QuestDB configuration + console.log('QuestDB settings:'); + console.log(`- Host: ${questdbConfig.QUESTDB_HOST}`); + console.log(`- HTTP port (web console): ${questdbConfig.QUESTDB_HTTP_PORT}`); + console.log(`- PostgreSQL port: ${questdbConfig.QUESTDB_PG_PORT}`); + console.log(`- InfluxDB port: ${questdbConfig.QUESTDB_INFLUX_PORT}`); + console.log(`- TLS enabled: ${questdbConfig.QUESTDB_TLS_ENABLED}`); + + // Example QuestDB client configuration + const questdbClientConfig = { + http: { + host: questdbConfig.QUESTDB_HOST, + port: questdbConfig.QUESTDB_HTTP_PORT, + tls: questdbConfig.QUESTDB_TLS_ENABLED, + timeout: questdbConfig.QUESTDB_REQUEST_TIMEOUT, + }, + postgresql: { + host: questdbConfig.QUESTDB_HOST, + port: questdbConfig.QUESTDB_PG_PORT, + database: questdbConfig.QUESTDB_DEFAULT_DATABASE, + user: questdbConfig.QUESTDB_USER, + password: questdbConfig.QUESTDB_PASSWORD, + }, + influxdb: { + host: questdbConfig.QUESTDB_HOST, + port: questdbConfig.QUESTDB_INFLUX_PORT, + } + }; + + console.log('QuestDB client configuration:', questdbClientConfig); + + // Example time-series table creation + const createTableQuery = ` + CREATE TABLE IF NOT EXISTS ohlcv_data ( + timestamp TIMESTAMP, + symbol SYMBOL, + open DOUBLE, + high DOUBLE, + low DOUBLE, + close DOUBLE, + volume LONG + ) timestamp(timestamp) PARTITION BY DAY; + `; + + console.log('Example table creation query:', createTableQuery); +} + +/** + * Example 9: MongoDB document database configuration + */ +function mongodbConfigurationExample() { + console.log('=== MongoDB Configuration Example ==='); + + // Access MongoDB configuration + console.log('MongoDB settings:'); + console.log(`- Host: ${mongodbConfig.MONGODB_HOST}`); + console.log(`- Port: ${mongodbConfig.MONGODB_PORT}`); + console.log(`- Database: ${mongodbConfig.MONGODB_DATABASE}`); + console.log(`- Username: ${mongodbConfig.MONGODB_USERNAME}`); + console.log(`- TLS enabled: ${mongodbConfig.MONGODB_TLS}`); + console.log(`- Max pool size: ${mongodbConfig.MONGODB_MAX_POOL_SIZE}`); + + // Build connection URI + const buildMongoUri = () => { + if (mongodbConfig.MONGODB_URI) { + return mongodbConfig.MONGODB_URI; + } + + const auth = mongodbConfig.MONGODB_USERNAME && mongodbConfig.MONGODB_PASSWORD + ? `${mongodbConfig.MONGODB_USERNAME}:${mongodbConfig.MONGODB_PASSWORD}@` + : ''; + + const tls = mongodbConfig.MONGODB_TLS ? '?tls=true' : ''; + + return `mongodb://${auth}${mongodbConfig.MONGODB_HOST}:${mongodbConfig.MONGODB_PORT}/${mongodbConfig.MONGODB_DATABASE}${tls}`; + }; + + const mongoUri = buildMongoUri(); + console.log('MongoDB connection URI:', mongoUri.replace(/:[^:@]*@/, ':***@')); // Hide password + + // Example MongoDB client configuration + const mongoClientConfig = { + maxPoolSize: mongodbConfig.MONGODB_MAX_POOL_SIZE, + minPoolSize: mongodbConfig.MONGODB_MIN_POOL_SIZE, + maxIdleTimeMS: mongodbConfig.MONGODB_MAX_IDLE_TIME, + connectTimeoutMS: mongodbConfig.MONGODB_CONNECT_TIMEOUT, + socketTimeoutMS: mongodbConfig.MONGODB_SOCKET_TIMEOUT, + serverSelectionTimeoutMS: mongodbConfig.MONGODB_SERVER_SELECTION_TIMEOUT, + retryWrites: mongodbConfig.MONGODB_RETRY_WRITES, + w: mongodbConfig.MONGODB_WRITE_CONCERN, + readPreference: mongodbConfig.MONGODB_READ_PREFERENCE, + }; + + console.log('MongoDB client configuration:', mongoClientConfig); + + // Example collections structure + const collections = [ + 'sentiment_data', // News sentiment analysis + 'market_news', // Raw news articles + 'social_signals', // Social media signals + 'earnings_reports', // Earnings data + 'analyst_ratings', // Analyst recommendations + ]; + + console.log('Example collections:', collections); +} + +/** + * Example 10: Dragonfly (Redis replacement) configuration + */ +function dragonflyConfigurationExample() { + console.log('=== Dragonfly Configuration Example ==='); + + // Access Dragonfly configuration + console.log('Dragonfly settings:'); + console.log(`- Host: ${dragonflyConfig.DRAGONFLY_HOST}`); + console.log(`- Port: ${dragonflyConfig.DRAGONFLY_PORT}`); + console.log(`- Database: ${dragonflyConfig.DRAGONFLY_DATABASE}`); + console.log(`- Cache mode: ${dragonflyConfig.DRAGONFLY_CACHE_MODE}`); + console.log(`- Max memory: ${dragonflyConfig.DRAGONFLY_MAX_MEMORY}`); + console.log(`- Pool size: ${dragonflyConfig.DRAGONFLY_POOL_SIZE}`); + // Example Dragonfly client configuration + const dragonflyClientConfig = { + host: dragonflyConfig.DRAGONFLY_HOST, + port: dragonflyConfig.DRAGONFLY_PORT, + db: dragonflyConfig.DRAGONFLY_DATABASE, + password: dragonflyConfig.DRAGONFLY_PASSWORD || undefined, + username: dragonflyConfig.DRAGONFLY_USERNAME || undefined, + retryDelayOnFailover: dragonflyConfig.DRAGONFLY_RETRY_DELAY, + maxRetriesPerRequest: dragonflyConfig.DRAGONFLY_MAX_RETRIES, + connectTimeout: dragonflyConfig.DRAGONFLY_CONNECT_TIMEOUT, + commandTimeout: dragonflyConfig.DRAGONFLY_COMMAND_TIMEOUT, + enableAutoPipelining: true, + }; + + console.log('Dragonfly client configuration:', dragonflyClientConfig); + + // Example cache key patterns + const cachePatterns = { + marketData: 'market:{symbol}:{timeframe}', + indicators: 'indicators:{symbol}:{indicator}:{period}', + positions: 'positions:{account_id}', + orders: 'orders:{order_id}', + rateLimit: 'rate_limit:{provider}:{endpoint}', + sessions: 'session:{user_id}', + }; + + console.log('Example cache key patterns:', cachePatterns); + + // Example TTL configurations + const ttlConfigs = { + marketData: 60, // 1 minute for real-time data + indicators: 300, // 5 minutes for calculated indicators + positions: 30, // 30 seconds for positions + orders: 86400, // 1 day for order history + rateLimit: 3600, // 1 hour for rate limiting + sessions: 1800, // 30 minutes for user sessions + }; + + console.log('Example TTL configurations (seconds):', ttlConfigs); +} + +/** + * Example 11: Monitoring stack configuration (Prometheus, Grafana, Loki) + */ +function monitoringConfigurationExample() { + console.log('=== Monitoring Configuration Example ==='); + + // Prometheus configuration + console.log('Prometheus settings:'); + console.log(`- Host: ${prometheusConfig.PROMETHEUS_HOST}`); + console.log(`- Port: ${prometheusConfig.PROMETHEUS_PORT}`); + console.log(`- Scrape interval: ${prometheusConfig.PROMETHEUS_SCRAPE_INTERVAL}`); + console.log(`- Retention time: ${prometheusConfig.PROMETHEUS_RETENTION_TIME}`); + + // Grafana configuration + console.log('\nGrafana settings:'); + console.log(`- Host: ${grafanaConfig.GRAFANA_HOST}`); + console.log(`- Port: ${grafanaConfig.GRAFANA_PORT}`); + console.log(`- Admin user: ${grafanaConfig.GRAFANA_ADMIN_USER}`); + console.log(`- Allow sign up: ${grafanaConfig.GRAFANA_ALLOW_SIGN_UP}`); + console.log(`- Database type: ${grafanaConfig.GRAFANA_DATABASE_TYPE}`); + + // Loki configuration + console.log('\nLoki settings:'); + console.log(`- Host: ${lokiConfig.LOKI_HOST}`); + console.log(`- Port: ${lokiConfig.LOKI_PORT}`); + console.log(`- Batch size: ${lokiConfig.LOKI_BATCH_SIZE}`); + console.log(`- Retention period: ${lokiConfig.LOKI_RETENTION_PERIOD}`); + + // Example monitoring endpoints + const monitoringEndpoints = { + prometheus: `http://${prometheusConfig.PROMETHEUS_HOST}:${prometheusConfig.PROMETHEUS_PORT}`, + grafana: `http://${grafanaConfig.GRAFANA_HOST}:${grafanaConfig.GRAFANA_PORT}`, + loki: `http://${lokiConfig.LOKI_HOST}:${lokiConfig.LOKI_PORT}`, + }; + + console.log('\nMonitoring endpoints:', monitoringEndpoints); + + // Example metrics configuration + const metricsConfig = { + defaultLabels: { + service: 'stock-bot', + environment: getEnvironment(), + version: process.env.npm_package_version || '1.0.0', + }, + collectDefaultMetrics: true, + prefix: 'stockbot_', + buckets: [0.1, 0.5, 1, 2, 5, 10, 30, 60], // Response time buckets in seconds + }; + + console.log('Example metrics configuration:', metricsConfig); +} + +/** + * Example 12: Multi-database service configuration + */ +function multiDatabaseServiceExample() { + console.log('=== Multi-Database Service Example ==='); + + // Complete database configuration for a microservice + const serviceConfig = { + service: { + name: 'market-data-processor', + version: '1.0.0', + environment: getEnvironment(), + }, + + // PostgreSQL for operational data + postgresql: { + host: databaseConfig.DB_HOST, + port: databaseConfig.DB_PORT, + database: databaseConfig.DB_NAME, + username: databaseConfig.DB_USER, + password: databaseConfig.DB_PASSWORD, + ssl: databaseConfig.DB_SSL, + pool: { + min: databaseConfig.DB_POOL_MIN, + max: databaseConfig.DB_POOL_MAX, + idleTimeout: databaseConfig.DB_POOL_IDLE_TIMEOUT, + }, + }, + + // QuestDB for time-series data + questdb: { + host: questdbConfig.QUESTDB_HOST, + httpPort: questdbConfig.QUESTDB_HTTP_PORT, + pgPort: questdbConfig.QUESTDB_PG_PORT, + database: questdbConfig.QUESTDB_DEFAULT_DATABASE, + timeout: questdbConfig.QUESTDB_REQUEST_TIMEOUT, + }, + + // MongoDB for document storage + mongodb: { + host: mongodbConfig.MONGODB_HOST, + port: mongodbConfig.MONGODB_PORT, + database: mongodbConfig.MONGODB_DATABASE, + username: mongodbConfig.MONGODB_USERNAME, + maxPoolSize: mongodbConfig.MONGODB_MAX_POOL_SIZE, + readPreference: mongodbConfig.MONGODB_READ_PREFERENCE, + }, + + // Dragonfly for caching + dragonfly: { + host: dragonflyConfig.DRAGONFLY_HOST, + port: dragonflyConfig.DRAGONFLY_PORT, + database: dragonflyConfig.DRAGONFLY_DATABASE, + poolSize: dragonflyConfig.DRAGONFLY_POOL_SIZE, + commandTimeout: dragonflyConfig.DRAGONFLY_COMMAND_TIMEOUT, + }, + + // Monitoring + monitoring: { + prometheus: { + pushGateway: `http://${prometheusConfig.PROMETHEUS_HOST}:${prometheusConfig.PROMETHEUS_PORT}`, + scrapeInterval: prometheusConfig.PROMETHEUS_SCRAPE_INTERVAL, + }, loki: { + host: lokiConfig.LOKI_HOST, + port: lokiConfig.LOKI_PORT, + batchSize: lokiConfig.LOKI_BATCH_SIZE, + labels: { + service: 'market-data-processor', + environment: getEnvironment(), + }, + }, + }, + }; + + console.log('Complete service configuration:', JSON.stringify(serviceConfig, null, 2)); + + // Example data flow + const dataFlow = { + ingestion: 'Market data → Dragonfly (cache) → QuestDB (storage)', + processing: 'QuestDB → Analysis → PostgreSQL (results) → MongoDB (metadata)', + serving: 'Dragonfly (cache) ← PostgreSQL/QuestDB ← API requests', + monitoring: 'All services → Prometheus → Grafana dashboards', + logging: 'All services → Loki → Grafana log viewer', + }; + + console.log('\nData flow patterns:', dataFlow); +} + +/** + * Example 8: Creating a service configuration object + */ +function serviceConfigurationExample() { + console.log('=== Service Configuration Example ==='); + + // Example: Market Data Gateway service configuration + const marketDataGatewayConfig = { + service: { + name: 'market-data-gateway', + port: 3001, + environment: getEnvironment(), + }, + database: { + host: databaseConfig.DB_HOST, + port: databaseConfig.DB_PORT, + name: databaseConfig.DB_NAME, + ssl: databaseConfig.DB_SSL, + }, logging: { + level: loggingConfig.LOG_LEVEL, + console: loggingConfig.LOG_CONSOLE, + loki: { + host: lokiConfig.LOKI_HOST, + port: lokiConfig.LOKI_PORT, + labels: { + service: 'market-data-gateway', + environment: getEnvironment(), + } + } + }, + dataProviders: { + default: DEFAULT_DATA_PROVIDER, + enabled: getEnabledProviders(), + timeout: dataProvidersConfig.DATA_PROVIDER_TIMEOUT, + retries: dataProvidersConfig.DATA_PROVIDER_RETRIES, + }, + cache: { + enabled: dataProvidersConfig.DATA_CACHE_ENABLED, + ttl: dataProvidersConfig.DATA_CACHE_TTL, + maxSize: dataProvidersConfig.DATA_CACHE_MAX_SIZE, + } + }; + + console.log('Market Data Gateway configuration:', JSON.stringify(marketDataGatewayConfig, null, 2)); +} + +/** + * Main example runner + */ +function runAllExamples() { + console.log('🚀 Stock Bot Configuration Examples\n'); + + try { + basicUsageExample(); + console.log('\n'); + + databaseConnectionExample(); + console.log('\n'); + + loggingSetupExample(); + console.log('\n'); + + riskManagementExample(); + console.log('\n'); + + dataProviderExample(); + console.log('\n'); + + environmentSpecificExample(); + console.log('\n'); + + configurationValidationExample(); + console.log('\n'); + + questdbConfigurationExample(); + console.log('\n'); + + mongodbConfigurationExample(); + console.log('\n'); + + dragonflyConfigurationExample(); + console.log('\n'); + + monitoringConfigurationExample(); + console.log('\n'); + + multiDatabaseServiceExample(); + console.log('\n'); + + serviceConfigurationExample(); + + } catch (error) { + console.error('Example execution error:', error); + } +} + +// Export the examples for use in other files +export { + basicUsageExample, + databaseConnectionExample, + loggingSetupExample, + riskManagementExample, + dataProviderExample, + environmentSpecificExample, + configurationValidationExample, + questdbConfigurationExample, + mongodbConfigurationExample, + dragonflyConfigurationExample, + monitoringConfigurationExample, + multiDatabaseServiceExample, + serviceConfigurationExample, + runAllExamples, +}; + +// Run examples if this file is executed directly +if (require.main === module) { + runAllExamples(); +} diff --git a/libs/config/src/index.ts b/libs/config/src/index.ts index d56269a..7682acc 100644 --- a/libs/config/src/index.ts +++ b/libs/config/src/index.ts @@ -1,24 +1,25 @@ /** * @stock-bot/config * - * Configuration management library for Stock Bot platform + * Configuration management library for Stock Bot platform using envalid */ // Core configuration functionality export * from './core'; -export * from './types'; // Database configurations export * from './database'; +export * from './questdb'; +export * from './mongodb'; +export * from './dragonfly'; + +// Logging and monitoring configurations +export * from './logging'; +export * from './loki'; +export * from './monitoring'; // Data provider configurations export * from './data-providers'; // Risk management configurations export * from './risk'; - -// Logging configurations -export * from './logging'; - -// Service-specific configurations -export * from './services/index'; diff --git a/libs/config/src/logging.ts b/libs/config/src/logging.ts index 62f0b5d..4c6bd94 100644 --- a/libs/config/src/logging.ts +++ b/libs/config/src/logging.ts @@ -1,102 +1,81 @@ /** - * Loki logging configuration for Stock Bot platform + * Logging configuration using envalid + * Application logging settings without Loki (Loki config is in monitoring.ts) */ -import { z } from 'zod'; -import { getEnvVar, getNumericEnvVar, getBooleanEnvVar, createConfigLoader, validateConfig } from './core'; +import { cleanEnv, str, bool, num } from 'envalid'; /** - * Loki configuration schema + * Logging configuration with validation and defaults */ -export const lokiConfigSchema = z.object({ - host: z.string().default('localhost'), - port: z.number().default(3100), - username: z.string().optional(), - password: z.string().optional(), - retentionDays: z.number().default(30), - labels: z.record(z.string()).default({}), - batchSize: z.number().default(100), - flushIntervalMs: z.number().default(5000) +export const loggingConfig = cleanEnv(process.env, { + // Basic Logging Settings + LOG_LEVEL: str({ + default: 'info', + choices: ['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'], + desc: 'Logging level' + }), + LOG_FORMAT: str({ + default: 'json', + choices: ['json', 'simple', 'combined'], + desc: 'Log output format' + }), + LOG_CONSOLE: bool({ default: true, desc: 'Enable console logging' }), + LOG_FILE: bool({ default: false, desc: 'Enable file logging' }), + + // File Logging Settings + LOG_FILE_PATH: str({ default: 'logs', desc: 'Log file directory path' }), + LOG_FILE_MAX_SIZE: str({ default: '20m', desc: 'Maximum log file size' }), + LOG_FILE_MAX_FILES: num({ default: 14, desc: 'Maximum number of log files to keep' }), + LOG_FILE_DATE_PATTERN: str({ default: 'YYYY-MM-DD', desc: 'Log file date pattern' }), + + // Error Logging + LOG_ERROR_FILE: bool({ default: true, desc: 'Enable separate error log file' }), + LOG_ERROR_STACK: bool({ default: true, desc: 'Include stack traces in error logs' }), + + // Performance Logging + LOG_PERFORMANCE: bool({ default: false, desc: 'Enable performance logging' }), + LOG_SQL_QUERIES: bool({ default: false, desc: 'Log SQL queries' }), + LOG_HTTP_REQUESTS: bool({ default: true, desc: 'Log HTTP requests' }), + + // Structured Logging + LOG_STRUCTURED: bool({ default: true, desc: 'Use structured logging format' }), + LOG_TIMESTAMP: bool({ default: true, desc: 'Include timestamps in logs' }), + LOG_CALLER_INFO: bool({ default: false, desc: 'Include caller information in logs' }), + + // Log Filtering + LOG_SILENT_MODULES: str({ default: '', desc: 'Comma-separated list of modules to silence' }), + LOG_VERBOSE_MODULES: str({ default: '', desc: 'Comma-separated list of modules for verbose logging' }), + + // Application Context + LOG_SERVICE_NAME: str({ default: 'stock-bot', desc: 'Service name for log context' }), + LOG_SERVICE_VERSION: str({ default: '1.0.0', desc: 'Service version for log context' }), + LOG_ENVIRONMENT: str({ default: 'development', desc: 'Environment for log context' }), }); -export type LokiConfig = z.infer; +// Export typed configuration object +export type LoggingConfig = typeof loggingConfig; -/** - * Logging configuration schema - */ -export const loggingConfigSchema = z.object({ - level: z.enum(['debug', 'info', 'warn', 'error']).default('info'), - console: z.boolean().default(true), - loki: lokiConfigSchema -}); - -export type LoggingConfig = z.infer; - -/** - * Parse labels from environment variable string - * Format: key1=value1,key2=value2 - */ -function parseLabels(labelsStr?: string): Record { - if (!labelsStr) return {}; - - const labels: Record = {}; - labelsStr.split(',').forEach(labelPair => { - const [key, value] = labelPair.trim().split('='); - if (key && value) { - labels[key] = value; - } - }); - - return labels; -} - -/** - * Default logging configuration - */ -const defaultLoggingConfig: LoggingConfig = { - level: 'info', - console: true, - loki: { - host: 'localhost', - port: 3100, - retentionDays: 30, - labels: {}, - batchSize: 100, - flushIntervalMs: 5000 - } -}; - -/** - * Load logging configuration from environment variables - */ -export function loadLoggingConfig(): LoggingConfig { - const config = { - level: (getEnvVar('LOG_LEVEL') || 'info') as 'debug' | 'info' | 'warn' | 'error', - console: getBooleanEnvVar('LOG_CONSOLE', true), - loki: { - host: getEnvVar('LOKI_HOST') || 'localhost', - port: getNumericEnvVar('LOKI_PORT', 3100), - username: getEnvVar('LOKI_USERNAME'), - password: getEnvVar('LOKI_PASSWORD'), - retentionDays: getNumericEnvVar('LOKI_RETENTION_DAYS', 30), - labels: parseLabels(getEnvVar('LOKI_LABELS')), - batchSize: getNumericEnvVar('LOKI_BATCH_SIZE', 100), - flushIntervalMs: getNumericEnvVar('LOKI_FLUSH_INTERVAL_MS', 5000) - } - }; - - return validateConfig(config, loggingConfigSchema); -} - -/** - * Creates a dynamic configuration loader for logging - */ -export const createLoggingConfig = createConfigLoader( - 'logging', - loggingConfigSchema, - defaultLoggingConfig -); - -/** - * Singleton logging configuration - */ -export const loggingConfig = loadLoggingConfig(); +// Export individual config values for convenience +export const { + LOG_LEVEL, + LOG_FORMAT, + LOG_CONSOLE, + LOG_FILE, + LOG_FILE_PATH, + LOG_FILE_MAX_SIZE, + LOG_FILE_MAX_FILES, + LOG_FILE_DATE_PATTERN, + LOG_ERROR_FILE, + LOG_ERROR_STACK, + LOG_PERFORMANCE, + LOG_SQL_QUERIES, + LOG_HTTP_REQUESTS, + LOG_STRUCTURED, + LOG_TIMESTAMP, + LOG_CALLER_INFO, + LOG_SILENT_MODULES, + LOG_VERBOSE_MODULES, + LOG_SERVICE_NAME, + LOG_SERVICE_VERSION, + LOG_ENVIRONMENT, +} = loggingConfig; \ No newline at end of file diff --git a/libs/config/src/loki.ts b/libs/config/src/loki.ts new file mode 100644 index 0000000..121e1d4 --- /dev/null +++ b/libs/config/src/loki.ts @@ -0,0 +1,61 @@ +/** + * Loki log aggregation configuration using envalid + * Centralized logging configuration for the Stock Bot platform + */ +import { cleanEnv, str, port, bool, num } from 'envalid'; + +/** + * Loki configuration with validation and defaults + */ +export const lokiConfig = cleanEnv(process.env, { + // Loki Server + LOKI_HOST: str({ default: 'localhost', desc: 'Loki host' }), + LOKI_PORT: port({ default: 3100, desc: 'Loki port' }), + LOKI_URL: str({ default: '', desc: 'Complete Loki URL (overrides host/port)' }), + + // Authentication + LOKI_USERNAME: str({ default: '', desc: 'Loki username (if auth enabled)' }), + LOKI_PASSWORD: str({ default: '', desc: 'Loki password (if auth enabled)' }), + LOKI_TENANT_ID: str({ default: '', desc: 'Loki tenant ID (for multi-tenancy)' }), + + // Push Configuration + LOKI_PUSH_TIMEOUT: num({ default: 10000, desc: 'Push timeout in ms' }), + LOKI_BATCH_SIZE: num({ default: 1024, desc: 'Batch size for log entries' }), + LOKI_BATCH_WAIT: num({ default: 1000, desc: 'Batch wait time in ms' }), + + // Retention Settings + LOKI_RETENTION_PERIOD: str({ default: '30d', desc: 'Log retention period' }), + LOKI_MAX_CHUNK_AGE: str({ default: '1h', desc: 'Maximum chunk age' }), + + // TLS Settings + LOKI_TLS_ENABLED: bool({ default: false, desc: 'Enable TLS for Loki' }), + LOKI_TLS_INSECURE: bool({ default: false, desc: 'Skip TLS verification' }), + + // Log Labels + LOKI_DEFAULT_LABELS: str({ default: '', desc: 'Default labels for all log entries (JSON format)' }), + LOKI_SERVICE_LABEL: str({ default: 'stock-bot', desc: 'Service label for log entries' }), + LOKI_ENVIRONMENT_LABEL: str({ default: 'development', desc: 'Environment label for log entries' }), +}); + +// Export typed configuration object +export type LokiConfig = typeof lokiConfig; + +// Export individual config values for convenience +export const { + LOKI_HOST, + LOKI_PORT, + LOKI_URL, + LOKI_USERNAME, + LOKI_PASSWORD, + LOKI_TENANT_ID, + LOKI_PUSH_TIMEOUT, + LOKI_BATCH_SIZE, + LOKI_BATCH_WAIT, + LOKI_RETENTION_PERIOD, + LOKI_MAX_CHUNK_AGE, + LOKI_TLS_ENABLED, + LOKI_TLS_INSECURE, + LOKI_DEFAULT_LABELS, + LOKI_SERVICE_LABEL, + LOKI_ENVIRONMENT_LABEL, +} = lokiConfig; diff --git a/libs/config/src/mongodb.ts b/libs/config/src/mongodb.ts new file mode 100644 index 0000000..637ed33 --- /dev/null +++ b/libs/config/src/mongodb.ts @@ -0,0 +1,75 @@ +/** + * MongoDB configuration using envalid + * Document storage for sentiment data, raw documents, and unstructured data + */ +import { cleanEnv, str, port, bool, num } from 'envalid'; + +/** + * MongoDB configuration with validation and defaults + */ +export const mongodbConfig = cleanEnv(process.env, { + // MongoDB Connection + MONGODB_HOST: str({ default: 'localhost', desc: 'MongoDB host' }), + MONGODB_PORT: port({ default: 27017, desc: 'MongoDB port' }), + MONGODB_DATABASE: str({ default: 'trading_documents', desc: 'MongoDB database name' }), + + // Authentication + MONGODB_USERNAME: str({ default: 'trading_admin', desc: 'MongoDB username' }), + MONGODB_PASSWORD: str({ default: '', desc: 'MongoDB password' }), + MONGODB_AUTH_SOURCE: str({ default: 'admin', desc: 'MongoDB authentication database' }), + + // Connection URI (alternative to individual settings) + MONGODB_URI: str({ default: '', desc: 'Complete MongoDB connection URI (overrides individual settings)' }), + + // Connection Pool Settings + MONGODB_MAX_POOL_SIZE: num({ default: 10, desc: 'Maximum connection pool size' }), + MONGODB_MIN_POOL_SIZE: num({ default: 0, desc: 'Minimum connection pool size' }), + MONGODB_MAX_IDLE_TIME: num({ default: 30000, desc: 'Maximum idle time for connections in ms' }), + + // Timeouts + MONGODB_CONNECT_TIMEOUT: num({ default: 10000, desc: 'Connection timeout in ms' }), + MONGODB_SOCKET_TIMEOUT: num({ default: 30000, desc: 'Socket timeout in ms' }), + MONGODB_SERVER_SELECTION_TIMEOUT: num({ default: 5000, desc: 'Server selection timeout in ms' }), + + // SSL/TLS Settings + MONGODB_TLS: bool({ default: false, desc: 'Enable TLS for MongoDB connection' }), + MONGODB_TLS_INSECURE: bool({ default: false, desc: 'Allow invalid certificates in TLS mode' }), + MONGODB_TLS_CA_FILE: str({ default: '', desc: 'Path to TLS CA certificate file' }), + + // Additional Settings + MONGODB_RETRY_WRITES: bool({ default: true, desc: 'Enable retryable writes' }), + MONGODB_JOURNAL: bool({ default: true, desc: 'Enable write concern journal' }), + MONGODB_READ_PREFERENCE: str({ + default: 'primary', + choices: ['primary', 'primaryPreferred', 'secondary', 'secondaryPreferred', 'nearest'], + desc: 'MongoDB read preference' + }), + MONGODB_WRITE_CONCERN: str({ default: 'majority', desc: 'Write concern level' }), +}); + +// Export typed configuration object +export type MongoDbConfig = typeof mongodbConfig; + +// Export individual config values for convenience +export const { + MONGODB_HOST, + MONGODB_PORT, + MONGODB_DATABASE, + MONGODB_USERNAME, + MONGODB_PASSWORD, + MONGODB_AUTH_SOURCE, + MONGODB_URI, + MONGODB_MAX_POOL_SIZE, + MONGODB_MIN_POOL_SIZE, + MONGODB_MAX_IDLE_TIME, + MONGODB_CONNECT_TIMEOUT, + MONGODB_SOCKET_TIMEOUT, + MONGODB_SERVER_SELECTION_TIMEOUT, + MONGODB_TLS, + MONGODB_TLS_INSECURE, + MONGODB_TLS_CA_FILE, + MONGODB_RETRY_WRITES, + MONGODB_JOURNAL, + MONGODB_READ_PREFERENCE, + MONGODB_WRITE_CONCERN, +} = mongodbConfig; diff --git a/libs/config/src/monitoring.ts b/libs/config/src/monitoring.ts new file mode 100644 index 0000000..5513cd2 --- /dev/null +++ b/libs/config/src/monitoring.ts @@ -0,0 +1,90 @@ +/** + * Monitoring configuration using envalid + * Prometheus metrics, Grafana visualization, and Loki logging + */ +import { cleanEnv, str, port, bool, num } from 'envalid'; + +/** + * Prometheus configuration with validation and defaults + */ +export const prometheusConfig = cleanEnv(process.env, { + // Prometheus Server + PROMETHEUS_HOST: str({ default: 'localhost', desc: 'Prometheus host' }), + PROMETHEUS_PORT: port({ default: 9090, desc: 'Prometheus port' }), + PROMETHEUS_URL: str({ default: '', desc: 'Complete Prometheus URL (overrides host/port)' }), + + // Authentication + PROMETHEUS_USERNAME: str({ default: '', desc: 'Prometheus username (if auth enabled)' }), + PROMETHEUS_PASSWORD: str({ default: '', desc: 'Prometheus password (if auth enabled)' }), + + // Metrics Collection + PROMETHEUS_SCRAPE_INTERVAL: str({ default: '15s', desc: 'Default scrape interval' }), + PROMETHEUS_EVALUATION_INTERVAL: str({ default: '15s', desc: 'Rule evaluation interval' }), + PROMETHEUS_RETENTION_TIME: str({ default: '15d', desc: 'Data retention time' }), + + // TLS Settings + PROMETHEUS_TLS_ENABLED: bool({ default: false, desc: 'Enable TLS for Prometheus' }), + PROMETHEUS_TLS_INSECURE: bool({ default: false, desc: 'Skip TLS verification' }), +}); + +/** + * Grafana configuration with validation and defaults + */ +export const grafanaConfig = cleanEnv(process.env, { + // Grafana Server + GRAFANA_HOST: str({ default: 'localhost', desc: 'Grafana host' }), + GRAFANA_PORT: port({ default: 3000, desc: 'Grafana port' }), + GRAFANA_URL: str({ default: '', desc: 'Complete Grafana URL (overrides host/port)' }), + + // Authentication + GRAFANA_ADMIN_USER: str({ default: 'admin', desc: 'Grafana admin username' }), + GRAFANA_ADMIN_PASSWORD: str({ default: 'admin', desc: 'Grafana admin password' }), + + // Security Settings + GRAFANA_ALLOW_SIGN_UP: bool({ default: false, desc: 'Allow user sign up' }), + GRAFANA_SECRET_KEY: str({ default: '', desc: 'Grafana secret key for encryption' }), + + // Database Settings + GRAFANA_DATABASE_TYPE: str({ + default: 'sqlite3', + choices: ['mysql', 'postgres', 'sqlite3'], + desc: 'Grafana database type' + }), + GRAFANA_DATABASE_URL: str({ default: '', desc: 'Grafana database URL' }), + + // Feature Flags + GRAFANA_DISABLE_GRAVATAR: bool({ default: true, desc: 'Disable Gravatar avatars' }), + GRAFANA_ENABLE_GZIP: bool({ default: true, desc: 'Enable gzip compression' }), +}); + +// Export typed configuration objects +export type PrometheusConfig = typeof prometheusConfig; +export type GrafanaConfig = typeof grafanaConfig; + +// Export individual config values for convenience +export const { + PROMETHEUS_HOST, + PROMETHEUS_PORT, + PROMETHEUS_URL, + PROMETHEUS_USERNAME, + PROMETHEUS_PASSWORD, + PROMETHEUS_SCRAPE_INTERVAL, + PROMETHEUS_EVALUATION_INTERVAL, + PROMETHEUS_RETENTION_TIME, + PROMETHEUS_TLS_ENABLED, + PROMETHEUS_TLS_INSECURE, +} = prometheusConfig; + +export const { + GRAFANA_HOST, + GRAFANA_PORT, + GRAFANA_URL, + GRAFANA_ADMIN_USER, + GRAFANA_ADMIN_PASSWORD, + GRAFANA_ALLOW_SIGN_UP, + GRAFANA_SECRET_KEY, + GRAFANA_DATABASE_TYPE, + GRAFANA_DATABASE_URL, + GRAFANA_DISABLE_GRAVATAR, + GRAFANA_ENABLE_GZIP, +} = grafanaConfig; diff --git a/libs/config/src/questdb.ts b/libs/config/src/questdb.ts new file mode 100644 index 0000000..1d6f994 --- /dev/null +++ b/libs/config/src/questdb.ts @@ -0,0 +1,53 @@ +/** + * QuestDB configuration using envalid + * Time-series database for OHLCV data, indicators, and performance metrics + */ +import { cleanEnv, str, port, bool, num } from 'envalid'; + +/** + * QuestDB configuration with validation and defaults + */ +export const questdbConfig = cleanEnv(process.env, { + // QuestDB Connection + QUESTDB_HOST: str({ default: 'localhost', desc: 'QuestDB host' }), + QUESTDB_HTTP_PORT: port({ default: 9000, desc: 'QuestDB HTTP port (web console)' }), + QUESTDB_PG_PORT: port({ default: 8812, desc: 'QuestDB PostgreSQL wire protocol port' }), + QUESTDB_INFLUX_PORT: port({ default: 9009, desc: 'QuestDB InfluxDB line protocol port' }), + + // Authentication (if enabled) + QUESTDB_USER: str({ default: '', desc: 'QuestDB username (if auth enabled)' }), + QUESTDB_PASSWORD: str({ default: '', desc: 'QuestDB password (if auth enabled)' }), + + // Connection Settings + QUESTDB_CONNECTION_TIMEOUT: num({ default: 5000, desc: 'Connection timeout in ms' }), + QUESTDB_REQUEST_TIMEOUT: num({ default: 30000, desc: 'Request timeout in ms' }), + QUESTDB_RETRY_ATTEMPTS: num({ default: 3, desc: 'Number of retry attempts' }), + + // TLS Settings + QUESTDB_TLS_ENABLED: bool({ default: false, desc: 'Enable TLS for QuestDB connection' }), + QUESTDB_TLS_VERIFY_SERVER_CERT: bool({ default: true, desc: 'Verify server certificate' }), + + // Database Settings + QUESTDB_DEFAULT_DATABASE: str({ default: 'qdb', desc: 'Default database name' }), + QUESTDB_TELEMETRY_ENABLED: bool({ default: false, desc: 'Enable telemetry' }), +}); + +// Export typed configuration object +export type QuestDbConfig = typeof questdbConfig; + +// Export individual config values for convenience +export const { + QUESTDB_HOST, + QUESTDB_HTTP_PORT, + QUESTDB_PG_PORT, + QUESTDB_INFLUX_PORT, + QUESTDB_USER, + QUESTDB_PASSWORD, + QUESTDB_CONNECTION_TIMEOUT, + QUESTDB_REQUEST_TIMEOUT, + QUESTDB_RETRY_ATTEMPTS, + QUESTDB_TLS_ENABLED, + QUESTDB_TLS_VERIFY_SERVER_CERT, + QUESTDB_DEFAULT_DATABASE, + QUESTDB_TELEMETRY_ENABLED, +} = questdbConfig; diff --git a/libs/config/src/risk.ts b/libs/config/src/risk.ts index e56f4db..1359a99 100644 --- a/libs/config/src/risk.ts +++ b/libs/config/src/risk.ts @@ -1,45 +1,82 @@ /** - * Risk management configuration for trading operations + * Risk management configuration using envalid */ -import { getNumericEnvVar, validateConfig, createConfigLoader } from './core'; -import { riskConfigSchema, RiskConfig } from './types'; +import { cleanEnv, str, num, bool } from 'envalid'; /** - * Default risk configuration + * Risk configuration with validation and defaults */ -const defaultRiskConfig: RiskConfig = { - maxDrawdown: 0.05, - maxPositionSize: 0.1, - maxLeverage: 1, - stopLossDefault: 0.02, - takeProfitDefault: 0.05 -}; +export const riskConfig = cleanEnv(process.env, { + // Position Sizing + RISK_MAX_POSITION_SIZE: num({ default: 0.1, desc: 'Maximum position size as percentage of portfolio' }), + RISK_MAX_PORTFOLIO_EXPOSURE: num({ default: 0.8, desc: 'Maximum portfolio exposure percentage' }), + RISK_MAX_SINGLE_ASSET_EXPOSURE: num({ default: 0.2, desc: 'Maximum exposure to single asset' }), + RISK_MAX_SECTOR_EXPOSURE: num({ default: 0.3, desc: 'Maximum exposure to single sector' }), + + // Stop Loss and Take Profit + RISK_DEFAULT_STOP_LOSS: num({ default: 0.05, desc: 'Default stop loss percentage' }), + RISK_DEFAULT_TAKE_PROFIT: num({ default: 0.15, desc: 'Default take profit percentage' }), + RISK_TRAILING_STOP_ENABLED: bool({ default: true, desc: 'Enable trailing stop losses' }), + RISK_TRAILING_STOP_DISTANCE: num({ default: 0.03, desc: 'Trailing stop distance percentage' }), + + // Risk Limits + RISK_MAX_DAILY_LOSS: num({ default: 0.05, desc: 'Maximum daily loss percentage' }), + RISK_MAX_WEEKLY_LOSS: num({ default: 0.1, desc: 'Maximum weekly loss percentage' }), + RISK_MAX_MONTHLY_LOSS: num({ default: 0.2, desc: 'Maximum monthly loss percentage' }), + + // Volatility Controls + RISK_MAX_VOLATILITY_THRESHOLD: num({ default: 0.4, desc: 'Maximum volatility threshold' }), + RISK_VOLATILITY_LOOKBACK_DAYS: num({ default: 20, desc: 'Volatility calculation lookback period' }), + + // Correlation Controls + RISK_MAX_CORRELATION_THRESHOLD: num({ default: 0.7, desc: 'Maximum correlation between positions' }), + RISK_CORRELATION_LOOKBACK_DAYS: num({ default: 60, desc: 'Correlation calculation lookback period' }), + + // Leverage Controls + RISK_MAX_LEVERAGE: num({ default: 2.0, desc: 'Maximum leverage allowed' }), + RISK_MARGIN_CALL_THRESHOLD: num({ default: 0.3, desc: 'Margin call threshold' }), + + // Circuit Breakers + RISK_CIRCUIT_BREAKER_ENABLED: bool({ default: true, desc: 'Enable circuit breakers' }), + RISK_CIRCUIT_BREAKER_LOSS_THRESHOLD: num({ default: 0.1, desc: 'Circuit breaker loss threshold' }), + RISK_CIRCUIT_BREAKER_COOLDOWN_MINUTES: num({ default: 60, desc: 'Circuit breaker cooldown period' }), + + // Risk Model + RISK_MODEL_TYPE: str({ + choices: ['var', 'cvar', 'expected_shortfall'], + default: 'var', + desc: 'Risk model type' + }), + RISK_CONFIDENCE_LEVEL: num({ default: 0.95, desc: 'Risk model confidence level' }), + RISK_TIME_HORIZON_DAYS: num({ default: 1, desc: 'Risk time horizon in days' }), +}); -/** - * Load risk configuration from environment variables - */ -export function loadRiskConfig(): RiskConfig { - const config: RiskConfig = { - maxDrawdown: getNumericEnvVar('RISK_MAX_DRAWDOWN', defaultRiskConfig.maxDrawdown), - maxPositionSize: getNumericEnvVar('RISK_MAX_POSITION_SIZE', defaultRiskConfig.maxPositionSize), - maxLeverage: getNumericEnvVar('RISK_MAX_LEVERAGE', defaultRiskConfig.maxLeverage), - stopLossDefault: getNumericEnvVar('RISK_STOP_LOSS_DEFAULT', defaultRiskConfig.stopLossDefault), - takeProfitDefault: getNumericEnvVar('RISK_TAKE_PROFIT_DEFAULT', defaultRiskConfig.takeProfitDefault) - }; +// Export typed configuration object +export type RiskConfig = typeof riskConfig; - return validateConfig(config, riskConfigSchema); -} - -/** - * Creates a dynamic configuration loader for risk management - */ -export const createRiskConfig = createConfigLoader( - 'risk', - riskConfigSchema, - defaultRiskConfig -); - -/** - * Singleton risk configuration - */ -export const riskConfig = loadRiskConfig(); +// Export individual config values for convenience +export const { + RISK_MAX_POSITION_SIZE, + RISK_MAX_PORTFOLIO_EXPOSURE, + RISK_MAX_SINGLE_ASSET_EXPOSURE, + RISK_MAX_SECTOR_EXPOSURE, + RISK_DEFAULT_STOP_LOSS, + RISK_DEFAULT_TAKE_PROFIT, + RISK_TRAILING_STOP_ENABLED, + RISK_TRAILING_STOP_DISTANCE, + RISK_MAX_DAILY_LOSS, + RISK_MAX_WEEKLY_LOSS, + RISK_MAX_MONTHLY_LOSS, + RISK_MAX_VOLATILITY_THRESHOLD, + RISK_VOLATILITY_LOOKBACK_DAYS, + RISK_MAX_CORRELATION_THRESHOLD, + RISK_CORRELATION_LOOKBACK_DAYS, + RISK_MAX_LEVERAGE, + RISK_MARGIN_CALL_THRESHOLD, + RISK_CIRCUIT_BREAKER_ENABLED, + RISK_CIRCUIT_BREAKER_LOSS_THRESHOLD, + RISK_CIRCUIT_BREAKER_COOLDOWN_MINUTES, + RISK_MODEL_TYPE, + RISK_CONFIDENCE_LEVEL, + RISK_TIME_HORIZON_DAYS, +} = riskConfig; diff --git a/libs/config/src/services/index.ts b/libs/config/src/services/index.ts deleted file mode 100644 index d0aad5c..0000000 --- a/libs/config/src/services/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Export all service-specific configurations - */ -export * from './market-data-gateway'; -export * from './risk-guardian'; diff --git a/libs/config/src/services/market-data-gateway.ts b/libs/config/src/services/market-data-gateway.ts deleted file mode 100644 index d444968..0000000 --- a/libs/config/src/services/market-data-gateway.ts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Market Data Gateway service configuration - */ -import { z } from 'zod'; -import { getEnvVar, getNumericEnvVar, getBooleanEnvVar, createConfigLoader } from '../core'; -import { Environment, BaseConfig } from '../types'; -import { getEnvironment } from '../core'; - -/** - * Market Data Gateway specific configuration schema - */ -export const marketDataGatewayConfigSchema = z.object({ - environment: z.nativeEnum(Environment), - logLevel: z.enum(['debug', 'info', 'warn', 'error']).default('info'), - service: z.object({ - name: z.string().default('market-data-gateway'), - version: z.string().default('1.0.0'), - port: z.number().default(4000) - }), - websocket: z.object({ - enabled: z.boolean().default(true), - path: z.string().default('/ws/market-data'), - heartbeatInterval: z.number().default(30000) - }), - throttling: z.object({ - maxRequestsPerMinute: z.number().default(300), - maxConnectionsPerIP: z.number().default(5) - }), - caching: z.object({ - enabled: z.boolean().default(true), - ttlSeconds: z.number().default(60) - }) -}); - -/** - * Market Data Gateway configuration type - */ -export type MarketDataGatewayConfig = z.infer; - -/** - * Default Market Data Gateway configuration - */ -const defaultConfig: Partial = { - environment: getEnvironment(), - logLevel: 'info', - service: { - name: 'market-data-gateway', - version: '1.0.0', - port: 4000 - }, - websocket: { - enabled: true, - path: '/ws/market-data', - heartbeatInterval: 30000 // 30 seconds - }, - throttling: { - maxRequestsPerMinute: 300, - maxConnectionsPerIP: 5 - }, - caching: { - enabled: true, - ttlSeconds: 60 - } -}; - -/** - * Load Market Data Gateway configuration - */ -export function loadMarketDataGatewayConfig(): MarketDataGatewayConfig { - return { - environment: getEnvironment(), - logLevel: (getEnvVar('LOG_LEVEL') || defaultConfig.logLevel) as 'debug' | 'info' | 'warn' | 'error', - service: { - name: getEnvVar('SERVICE_NAME') || defaultConfig.service!.name, - version: getEnvVar('SERVICE_VERSION') || defaultConfig.service!.version, - port: getNumericEnvVar('SERVICE_PORT', defaultConfig.service!.port) - }, - websocket: { - enabled: getBooleanEnvVar('WEBSOCKET_ENABLED', defaultConfig.websocket!.enabled), - path: getEnvVar('WEBSOCKET_PATH') || defaultConfig.websocket!.path, - heartbeatInterval: getNumericEnvVar('WEBSOCKET_HEARTBEAT_INTERVAL', defaultConfig.websocket!.heartbeatInterval) - }, - throttling: { - maxRequestsPerMinute: getNumericEnvVar('THROTTLING_MAX_REQUESTS', defaultConfig.throttling!.maxRequestsPerMinute), - maxConnectionsPerIP: getNumericEnvVar('THROTTLING_MAX_CONNECTIONS', defaultConfig.throttling!.maxConnectionsPerIP) - }, - caching: { - enabled: getBooleanEnvVar('CACHING_ENABLED', defaultConfig.caching!.enabled), - ttlSeconds: getNumericEnvVar('CACHING_TTL_SECONDS', defaultConfig.caching!.ttlSeconds) - } - }; -} - -/** - * Creates a dynamic configuration loader for the Market Data Gateway - */ -export const createMarketDataGatewayConfig = createConfigLoader( - 'market-data-gateway', - marketDataGatewayConfigSchema, - defaultConfig -); - -/** - * Singleton Market Data Gateway configuration - */ -export const marketDataGatewayConfig = loadMarketDataGatewayConfig(); diff --git a/libs/config/src/services/risk-guardian.ts b/libs/config/src/services/risk-guardian.ts deleted file mode 100644 index 00fe887..0000000 --- a/libs/config/src/services/risk-guardian.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Risk Guardian service configuration - */ -import { z } from 'zod'; -import { getEnvVar, getNumericEnvVar, getBooleanEnvVar, createConfigLoader } from '../core'; -import { Environment, BaseConfig } from '../types'; -import { getEnvironment } from '../core'; - -/** - * Risk Guardian specific configuration schema - */ -export const riskGuardianConfigSchema = z.object({ - environment: z.nativeEnum(Environment), - logLevel: z.enum(['debug', 'info', 'warn', 'error']).default('info'), - service: z.object({ - name: z.string().default('risk-guardian'), - version: z.string().default('1.0.0'), - port: z.number().default(4001) - }), - riskChecks: z.object({ - preTradeValidation: z.boolean().default(true), - portfolioValidation: z.boolean().default(true), - leverageValidation: z.boolean().default(true), - concentrationValidation: z.boolean().default(true) - }), - alerting: z.object({ - enabled: z.boolean().default(true), - criticalThreshold: z.number().default(0.8), - warningThreshold: z.number().default(0.6) - }), - watchdog: z.object({ - enabled: z.boolean().default(true), - checkIntervalSeconds: z.number().default(60) - }) -}); - -/** - * Risk Guardian configuration type - */ -export type RiskGuardianConfig = z.infer; - -/** - * Default Risk Guardian configuration - */ -const defaultConfig: Partial = { - environment: getEnvironment(), - logLevel: 'info', - service: { - name: 'risk-guardian', - version: '1.0.0', - port: 4001 - }, - riskChecks: { - preTradeValidation: true, - portfolioValidation: true, - leverageValidation: true, - concentrationValidation: true - }, - alerting: { - enabled: true, - criticalThreshold: 0.8, - warningThreshold: 0.6 - }, - watchdog: { - enabled: true, - checkIntervalSeconds: 60 - } -}; - -/** - * Load Risk Guardian configuration - */ -export function loadRiskGuardianConfig(): RiskGuardianConfig { - return { - environment: getEnvironment(), - logLevel: (getEnvVar('LOG_LEVEL') || defaultConfig.logLevel) as 'debug' | 'info' | 'warn' | 'error', - service: { - name: getEnvVar('SERVICE_NAME') || defaultConfig.service!.name, - version: getEnvVar('SERVICE_VERSION') || defaultConfig.service!.version, - port: getNumericEnvVar('SERVICE_PORT', defaultConfig.service!.port) - }, - riskChecks: { - preTradeValidation: getBooleanEnvVar('RISK_CHECKS_PRE_TRADE', defaultConfig.riskChecks!.preTradeValidation), - portfolioValidation: getBooleanEnvVar('RISK_CHECKS_PORTFOLIO', defaultConfig.riskChecks!.portfolioValidation), - leverageValidation: getBooleanEnvVar('RISK_CHECKS_LEVERAGE', defaultConfig.riskChecks!.leverageValidation), - concentrationValidation: getBooleanEnvVar('RISK_CHECKS_CONCENTRATION', defaultConfig.riskChecks!.concentrationValidation) - }, - alerting: { - enabled: getBooleanEnvVar('ALERTING_ENABLED', defaultConfig.alerting!.enabled), - criticalThreshold: getNumericEnvVar('ALERTING_CRITICAL_THRESHOLD', defaultConfig.alerting!.criticalThreshold), - warningThreshold: getNumericEnvVar('ALERTING_WARNING_THRESHOLD', defaultConfig.alerting!.warningThreshold) - }, - watchdog: { - enabled: getBooleanEnvVar('WATCHDOG_ENABLED', defaultConfig.watchdog!.enabled), - checkIntervalSeconds: getNumericEnvVar('WATCHDOG_CHECK_INTERVAL', defaultConfig.watchdog!.checkIntervalSeconds) - } - }; -} - -/** - * Creates a dynamic configuration loader for the Risk Guardian - */ -export const createRiskGuardianConfig = createConfigLoader( - 'risk-guardian', - riskGuardianConfigSchema, - defaultConfig -); - -/** - * Singleton Risk Guardian configuration - */ -export const riskGuardianConfig = loadRiskGuardianConfig(); diff --git a/libs/config/src/types.ts b/libs/config/src/types.ts deleted file mode 100644 index 9308167..0000000 --- a/libs/config/src/types.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Configuration type definitions for the Stock Bot platform - */ -import { z } from 'zod'; - -/** - * Environment enum for different deployment environments - */ -export enum Environment { - Development = 'development', - Testing = 'testing', - Staging = 'staging', - Production = 'production' -} - -/** - * Common configuration interface for all service configs - */ -export interface BaseConfig { - environment: Environment; - logLevel: 'debug' | 'info' | 'warn' | 'error'; - service: { - name: string; - version: string; - port: number; - }; -} - -/** - * Database configuration schema - */ -export const databaseConfigSchema = z.object({ - dragonfly: z.object({ - host: z.string().default('localhost'), - port: z.number().default(6379), - password: z.string().optional(), - maxRetriesPerRequest: z.number().default(3) - }), - questDB: z.object({ - host: z.string().default('localhost'), - port: z.number().default(8812), - database: z.string().default('stockbot'), - user: z.string().default('admin'), - password: z.string().optional(), - httpPort: z.number().default(9000) - }), - mongodb: z.object({ - uri: z.string().default('mongodb://localhost:27017'), - database: z.string().default('stockbot'), - username: z.string().optional(), - password: z.string().optional(), - options: z.record(z.string(), z.any()).optional() - }), - postgres: z.object({ - host: z.string().default('localhost'), - port: z.number().default(5432), - database: z.string().default('stockbot'), - user: z.string().default('postgres'), - password: z.string().optional(), - ssl: z.boolean().default(false), - poolSize: z.number().default(10) - }) -}); - -/** - * Data provider configuration schema - */ -export const dataProviderSchema = z.object({ - name: z.string(), - type: z.enum(['rest', 'websocket', 'file']), - baseUrl: z.string().url().optional(), - wsUrl: z.string().url().optional(), - apiKey: z.string().optional(), - apiSecret: z.string().optional(), - refreshInterval: z.number().optional(), - rateLimits: z.object({ - maxRequestsPerMinute: z.number().optional(), - maxRequestsPerSecond: z.number().optional() - }).optional() -}); - -export const dataProvidersConfigSchema = z.object({ - providers: z.array(dataProviderSchema), - defaultProvider: z.string() -}); - -/** - * Risk management configuration schema - */ -export const riskConfigSchema = z.object({ - maxDrawdown: z.number().default(0.05), - maxPositionSize: z.number().default(0.1), - maxLeverage: z.number().default(1), - stopLossDefault: z.number().default(0.02), - takeProfitDefault: z.number().default(0.05) -}); - -/** - * Type definitions based on schemas - */ -export type DatabaseConfig = z.infer; -export type DataProviderConfig = z.infer; -export type DataProvidersConfig = z.infer; -export type RiskConfig = z.infer; diff --git a/libs/config/tsconfig.tsbuildinfo b/libs/config/tsconfig.tsbuildinfo new file mode 100644 index 0000000..d2e84bc --- /dev/null +++ b/libs/config/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.dom.asynciterable.d.ts","../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../node_modules/typescript/lib/lib.scripthost.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/typescript/lib/lib.es2022.full.d.ts","../../node_modules/envalid/dist/types.d.ts","../../node_modules/envalid/dist/envalid.d.ts","../../node_modules/envalid/dist/errors.d.ts","../../node_modules/envalid/dist/middleware.d.ts","../../node_modules/envalid/dist/validators.d.ts","../../node_modules/envalid/dist/reporter.d.ts","../../node_modules/envalid/dist/makers.d.ts","../../node_modules/envalid/dist/index.d.ts","./src/admin-interfaces.ts","../../node_modules/@types/node/compatibility/disposable.d.ts","../../node_modules/@types/node/compatibility/indexable.d.ts","../../node_modules/@types/node/compatibility/iterators.d.ts","../../node_modules/@types/node/compatibility/index.d.ts","../../node_modules/@types/node/globals.typedarray.d.ts","../../node_modules/@types/node/buffer.buffer.d.ts","../../node_modules/buffer/index.d.ts","../../node_modules/undici-types/header.d.ts","../../node_modules/undici-types/readable.d.ts","../../node_modules/undici-types/file.d.ts","../../node_modules/undici-types/fetch.d.ts","../../node_modules/undici-types/formdata.d.ts","../../node_modules/undici-types/connector.d.ts","../../node_modules/undici-types/client.d.ts","../../node_modules/undici-types/errors.d.ts","../../node_modules/undici-types/dispatcher.d.ts","../../node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/undici-types/global-origin.d.ts","../../node_modules/undici-types/pool-stats.d.ts","../../node_modules/undici-types/pool.d.ts","../../node_modules/undici-types/handlers.d.ts","../../node_modules/undici-types/balanced-pool.d.ts","../../node_modules/undici-types/agent.d.ts","../../node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/undici-types/mock-agent.d.ts","../../node_modules/undici-types/mock-client.d.ts","../../node_modules/undici-types/mock-pool.d.ts","../../node_modules/undici-types/mock-errors.d.ts","../../node_modules/undici-types/proxy-agent.d.ts","../../node_modules/undici-types/env-http-proxy-agent.d.ts","../../node_modules/undici-types/retry-handler.d.ts","../../node_modules/undici-types/retry-agent.d.ts","../../node_modules/undici-types/api.d.ts","../../node_modules/undici-types/interceptors.d.ts","../../node_modules/undici-types/util.d.ts","../../node_modules/undici-types/cookies.d.ts","../../node_modules/undici-types/patch.d.ts","../../node_modules/undici-types/websocket.d.ts","../../node_modules/undici-types/eventsource.d.ts","../../node_modules/undici-types/filereader.d.ts","../../node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/undici-types/content-type.d.ts","../../node_modules/undici-types/cache.d.ts","../../node_modules/undici-types/index.d.ts","../../node_modules/@types/node/globals.d.ts","../../node_modules/@types/node/assert.d.ts","../../node_modules/@types/node/assert/strict.d.ts","../../node_modules/@types/node/async_hooks.d.ts","../../node_modules/@types/node/buffer.d.ts","../../node_modules/@types/node/child_process.d.ts","../../node_modules/@types/node/cluster.d.ts","../../node_modules/@types/node/console.d.ts","../../node_modules/@types/node/constants.d.ts","../../node_modules/@types/node/crypto.d.ts","../../node_modules/@types/node/dgram.d.ts","../../node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/@types/node/dns.d.ts","../../node_modules/@types/node/dns/promises.d.ts","../../node_modules/@types/node/domain.d.ts","../../node_modules/@types/node/dom-events.d.ts","../../node_modules/@types/node/events.d.ts","../../node_modules/@types/node/fs.d.ts","../../node_modules/@types/node/fs/promises.d.ts","../../node_modules/@types/node/http.d.ts","../../node_modules/@types/node/http2.d.ts","../../node_modules/@types/node/https.d.ts","../../node_modules/@types/node/inspector.d.ts","../../node_modules/@types/node/module.d.ts","../../node_modules/@types/node/net.d.ts","../../node_modules/@types/node/os.d.ts","../../node_modules/@types/node/path.d.ts","../../node_modules/@types/node/perf_hooks.d.ts","../../node_modules/@types/node/process.d.ts","../../node_modules/@types/node/punycode.d.ts","../../node_modules/@types/node/querystring.d.ts","../../node_modules/@types/node/readline.d.ts","../../node_modules/@types/node/readline/promises.d.ts","../../node_modules/@types/node/repl.d.ts","../../node_modules/@types/node/sea.d.ts","../../node_modules/@types/node/stream.d.ts","../../node_modules/@types/node/stream/promises.d.ts","../../node_modules/@types/node/stream/consumers.d.ts","../../node_modules/@types/node/stream/web.d.ts","../../node_modules/@types/node/string_decoder.d.ts","../../node_modules/@types/node/test.d.ts","../../node_modules/@types/node/timers.d.ts","../../node_modules/@types/node/timers/promises.d.ts","../../node_modules/@types/node/tls.d.ts","../../node_modules/@types/node/trace_events.d.ts","../../node_modules/@types/node/tty.d.ts","../../node_modules/@types/node/url.d.ts","../../node_modules/@types/node/util.d.ts","../../node_modules/@types/node/v8.d.ts","../../node_modules/@types/node/vm.d.ts","../../node_modules/@types/node/wasi.d.ts","../../node_modules/@types/node/worker_threads.d.ts","../../node_modules/@types/node/zlib.d.ts","../../node_modules/@types/node/index.d.ts","../../node_modules/dotenv/lib/main.d.ts","./src/core.ts","./src/data-providers.ts","./src/database.ts","./src/dragonfly.ts","./src/questdb.ts","./src/mongodb.ts","./src/monitoring.ts","./src/loki.ts","./src/logging.ts","./src/risk.ts","./src/example.ts","./src/index.ts","../../node_modules/@babel/types/lib/index.d.ts","../../node_modules/@types/babel__generator/index.d.ts","../../node_modules/@babel/parser/typings/babel-parser.d.ts","../../node_modules/@types/babel__template/index.d.ts","../../node_modules/@types/babel__traverse/index.d.ts","../../node_modules/@types/babel__core/index.d.ts","../../node_modules/@types/connect/index.d.ts","../../node_modules/@types/body-parser/index.d.ts","../../node_modules/ioredis/built/types.d.ts","../../node_modules/ioredis/built/command.d.ts","../../node_modules/ioredis/built/scanstream.d.ts","../../node_modules/ioredis/built/utils/rediscommander.d.ts","../../node_modules/ioredis/built/transaction.d.ts","../../node_modules/ioredis/built/utils/commander.d.ts","../../node_modules/ioredis/built/connectors/abstractconnector.d.ts","../../node_modules/ioredis/built/connectors/connectorconstructor.d.ts","../../node_modules/ioredis/built/connectors/sentinelconnector/types.d.ts","../../node_modules/ioredis/built/connectors/sentinelconnector/sentineliterator.d.ts","../../node_modules/ioredis/built/connectors/sentinelconnector/index.d.ts","../../node_modules/ioredis/built/connectors/standaloneconnector.d.ts","../../node_modules/ioredis/built/redis/redisoptions.d.ts","../../node_modules/ioredis/built/cluster/util.d.ts","../../node_modules/ioredis/built/cluster/clusteroptions.d.ts","../../node_modules/ioredis/built/cluster/index.d.ts","../../node_modules/denque/index.d.ts","../../node_modules/ioredis/built/subscriptionset.d.ts","../../node_modules/ioredis/built/datahandler.d.ts","../../node_modules/ioredis/built/redis.d.ts","../../node_modules/ioredis/built/pipeline.d.ts","../../node_modules/ioredis/built/index.d.ts","../../node_modules/bull/index.d.ts","../../node_modules/bun-types/globals.d.ts","../../node_modules/bun-types/s3.d.ts","../../node_modules/bun-types/fetch.d.ts","../../node_modules/bun-types/bun.d.ts","../../node_modules/bun-types/extensions.d.ts","../../node_modules/bun-types/devserver.d.ts","../../node_modules/bun-types/ffi.d.ts","../../node_modules/bun-types/html-rewriter.d.ts","../../node_modules/bun-types/jsc.d.ts","../../node_modules/bun-types/sqlite.d.ts","../../node_modules/bun-types/test.d.ts","../../node_modules/bun-types/wasm.d.ts","../../node_modules/bun-types/overrides.d.ts","../../node_modules/bun-types/deprecated.d.ts","../../node_modules/bun-types/redis.d.ts","../../node_modules/bun-types/shell.d.ts","../../node_modules/bun-types/bun.ns.d.ts","../../node_modules/bun-types/index.d.ts","../../node_modules/@types/bun/index.d.ts","../../node_modules/@types/mime/index.d.ts","../../node_modules/@types/send/index.d.ts","../../node_modules/@types/qs/index.d.ts","../../node_modules/@types/range-parser/index.d.ts","../../node_modules/@types/express-serve-static-core/index.d.ts","../../node_modules/@types/http-errors/index.d.ts","../../node_modules/@types/serve-static/index.d.ts","../../node_modules/@types/express/index.d.ts","../../node_modules/@types/compression/index.d.ts","../../node_modules/@types/cors/index.d.ts","../../node_modules/@types/luxon/src/zone.d.ts","../../node_modules/@types/luxon/src/settings.d.ts","../../node_modules/@types/luxon/src/_util.d.ts","../../node_modules/@types/luxon/src/misc.d.ts","../../node_modules/@types/luxon/src/duration.d.ts","../../node_modules/@types/luxon/src/interval.d.ts","../../node_modules/@types/luxon/src/datetime.d.ts","../../node_modules/@types/luxon/src/info.d.ts","../../node_modules/@types/luxon/src/luxon.d.ts","../../node_modules/@types/luxon/index.d.ts","../../node_modules/cron/dist/constants.d.ts","../../node_modules/cron/dist/types/utils.d.ts","../../node_modules/cron/dist/types/cron.types.d.ts","../../node_modules/cron/dist/time.d.ts","../../node_modules/cron/dist/job.d.ts","../../node_modules/cron/dist/index.d.ts","../../node_modules/@types/d3-array/index.d.ts","../../node_modules/@types/d3-color/index.d.ts","../../node_modules/@types/d3-ease/index.d.ts","../../node_modules/@types/d3-interpolate/index.d.ts","../../node_modules/@types/d3-path/index.d.ts","../../node_modules/@types/d3-time/index.d.ts","../../node_modules/@types/d3-scale/index.d.ts","../../node_modules/@types/d3-shape/index.d.ts","../../node_modules/@types/d3-timer/index.d.ts","../../node_modules/@types/estree/index.d.ts","../../node_modules/@types/graceful-fs/index.d.ts","../../node_modules/@types/istanbul-lib-coverage/index.d.ts","../../node_modules/@types/istanbul-lib-report/index.d.ts","../../node_modules/@types/istanbul-reports/index.d.ts","../../node_modules/@types/jasmine/index.d.ts","../../node_modules/@jest/expect-utils/build/index.d.ts","../../node_modules/chalk/index.d.ts","../../node_modules/@sinclair/typebox/typebox.d.ts","../../node_modules/@jest/schemas/build/index.d.ts","../../node_modules/pretty-format/build/index.d.ts","../../node_modules/jest-diff/build/index.d.ts","../../node_modules/jest-matcher-utils/build/index.d.ts","../../node_modules/expect/build/index.d.ts","../../node_modules/@types/jest/index.d.ts","../../node_modules/@types/json-schema/index.d.ts","../../node_modules/@types/lodash/common/common.d.ts","../../node_modules/@types/lodash/common/array.d.ts","../../node_modules/@types/lodash/common/collection.d.ts","../../node_modules/@types/lodash/common/date.d.ts","../../node_modules/@types/lodash/common/function.d.ts","../../node_modules/@types/lodash/common/lang.d.ts","../../node_modules/@types/lodash/common/math.d.ts","../../node_modules/@types/lodash/common/number.d.ts","../../node_modules/@types/lodash/common/object.d.ts","../../node_modules/@types/lodash/common/seq.d.ts","../../node_modules/@types/lodash/common/string.d.ts","../../node_modules/@types/lodash/common/util.d.ts","../../node_modules/@types/lodash/index.d.ts","../../node_modules/@types/node-cron/index.d.ts","../../node_modules/@types/react/global.d.ts","../../node_modules/csstype/index.d.ts","../../node_modules/@types/react/index.d.ts","../../node_modules/@types/react-dom/index.d.ts","../../node_modules/@types/semver/classes/semver.d.ts","../../node_modules/@types/semver/functions/parse.d.ts","../../node_modules/@types/semver/functions/valid.d.ts","../../node_modules/@types/semver/functions/clean.d.ts","../../node_modules/@types/semver/functions/inc.d.ts","../../node_modules/@types/semver/functions/diff.d.ts","../../node_modules/@types/semver/functions/major.d.ts","../../node_modules/@types/semver/functions/minor.d.ts","../../node_modules/@types/semver/functions/patch.d.ts","../../node_modules/@types/semver/functions/prerelease.d.ts","../../node_modules/@types/semver/functions/compare.d.ts","../../node_modules/@types/semver/functions/rcompare.d.ts","../../node_modules/@types/semver/functions/compare-loose.d.ts","../../node_modules/@types/semver/functions/compare-build.d.ts","../../node_modules/@types/semver/functions/sort.d.ts","../../node_modules/@types/semver/functions/rsort.d.ts","../../node_modules/@types/semver/functions/gt.d.ts","../../node_modules/@types/semver/functions/lt.d.ts","../../node_modules/@types/semver/functions/eq.d.ts","../../node_modules/@types/semver/functions/neq.d.ts","../../node_modules/@types/semver/functions/gte.d.ts","../../node_modules/@types/semver/functions/lte.d.ts","../../node_modules/@types/semver/functions/cmp.d.ts","../../node_modules/@types/semver/functions/coerce.d.ts","../../node_modules/@types/semver/classes/comparator.d.ts","../../node_modules/@types/semver/classes/range.d.ts","../../node_modules/@types/semver/functions/satisfies.d.ts","../../node_modules/@types/semver/ranges/max-satisfying.d.ts","../../node_modules/@types/semver/ranges/min-satisfying.d.ts","../../node_modules/@types/semver/ranges/to-comparators.d.ts","../../node_modules/@types/semver/ranges/min-version.d.ts","../../node_modules/@types/semver/ranges/valid.d.ts","../../node_modules/@types/semver/ranges/outside.d.ts","../../node_modules/@types/semver/ranges/gtr.d.ts","../../node_modules/@types/semver/ranges/ltr.d.ts","../../node_modules/@types/semver/ranges/intersects.d.ts","../../node_modules/@types/semver/ranges/simplify.d.ts","../../node_modules/@types/semver/ranges/subset.d.ts","../../node_modules/@types/semver/internals/identifiers.d.ts","../../node_modules/@types/semver/index.d.ts","../../node_modules/@types/stack-utils/index.d.ts","../../node_modules/@types/uuid/index.d.ts","../../node_modules/@types/ws/index.d.ts","../../node_modules/@types/yargs-parser/index.d.ts","../../node_modules/@types/yargs/index.d.ts"],"fileIdsList":[[71,78,121,215,216,217,218,220,226,228,229,230],[78,121,143,171,215,216,217,218,220,226,228,229,230],[78,121,172,173,174,175,176,177,178,179,180,181,215,216,217,218,220,226,228,229,230],[78,121,184,215,216,217,218,220,226,228,229,230],[78,121,215,216,217,218,220,226,228,229,230],[78,121,215,216,217,218,220,226,228,229,230,277],[78,121,184,185,186,187,188,215,216,217,218,220,226,228,229,230],[78,121,184,186,215,216,217,218,220,226,228,229,230],[78,121,136,170,190,215,216,217,218,220,226,228,229,230],[78,121,215,216,217,218,220,226,228,229,230,232],[78,121,169,215,216,217,218,220,226,228,229,230,241],[78,121,136,170,215,216,217,218,220,226,228,229,230],[78,121,215,216,217,218,220,226,228,229,230,261],[78,121,215,216,217,218,220,226,228,229,230,265],[78,121,215,216,217,218,220,226,228,229,230,264],[78,121,133,136,170,215,216,217,218,220,226,228,229,230,235,236,237],[78,121,191,215,216,217,218,220,226,228,229,230,238,240],[78,121,134,170,215,216,217,218,220,226,228,229,230],[78,121,215,216,217,218,220,226,228,229,230,271],[78,121,215,216,217,218,220,226,228,229,230,272],[78,121,215,216,217,218,220,226,228,229,230,279,282],[78,121,215,216,217,218,220,226,228,229,230,285,287,288,289,290,291,292,293,294,295,296,297],[78,121,215,216,217,218,220,226,228,229,230,285,286,288,289,290,291,292,293,294,295,296,297],[78,121,215,216,217,218,220,226,228,229,230,286,287,288,289,290,291,292,293,294,295,296,297],[78,121,215,216,217,218,220,226,228,229,230,285,286,287,289,290,291,292,293,294,295,296,297],[78,121,215,216,217,218,220,226,228,229,230,285,286,287,288,290,291,292,293,294,295,296,297],[78,121,215,216,217,218,220,226,228,229,230,285,286,287,288,289,291,292,293,294,295,296,297],[78,121,215,216,217,218,220,226,228,229,230,285,286,287,288,289,290,292,293,294,295,296,297],[78,121,215,216,217,218,220,226,228,229,230,285,286,287,288,289,290,291,293,294,295,296,297],[78,121,215,216,217,218,220,226,228,229,230,285,286,287,288,289,290,291,292,294,295,296,297],[78,121,215,216,217,218,220,226,228,229,230,285,286,287,288,289,290,291,292,293,295,296,297],[78,121,215,216,217,218,220,226,228,229,230,285,286,287,288,289,290,291,292,293,294,296,297],[78,121,215,216,217,218,220,226,228,229,230,285,286,287,288,289,290,291,292,293,294,295,297],[78,121,215,216,217,218,220,226,228,229,230,285,286,287,288,289,290,291,292,293,294,295,296],[78,121,215,216,217,218,220,226,228,229,230,252],[78,121,215,216,217,218,220,226,228,229,230,245],[78,121,215,216,217,218,220,226,228,229,230,244,246,248,249,253],[78,121,215,216,217,218,220,226,228,229,230,246,247,250],[78,121,215,216,217,218,220,226,228,229,230,244,247,250],[78,121,215,216,217,218,220,226,228,229,230,246,248,250],[78,121,215,216,217,218,220,226,228,229,230,244,245,247,248,249,250,251],[78,121,215,216,217,218,220,226,228,229,230,244,250],[78,121,215,216,217,218,220,226,228,229,230,246],[78,121,133,215,216,217,218,220,226,228,229,230],[78,118,121,215,216,217,218,220,226,228,229,230],[78,120,121,215,216,217,218,220,226,228,229,230],[121,215,216,217,218,220,226,228,229,230],[78,121,126,155,215,216,217,218,220,226,228,229,230],[78,121,122,127,133,134,141,152,163,215,216,217,218,220,226,228,229,230],[78,121,122,123,133,141,215,216,217,218,220,226,228,229,230],[73,74,75,78,121,215,216,217,218,220,226,228,229,230],[78,121,124,164,215,216,217,218,220,226,228,229,230],[78,121,125,126,134,142,215,216,217,218,220,226,228,229,230],[78,121,126,152,160,215,216,217,218,220,226,228,229,230],[78,121,127,129,133,141,215,216,217,218,220,226,228,229,230],[78,120,121,128,215,216,217,218,220,226,228,229,230],[78,121,129,130,215,216,217,218,220,226,228,229,230],[78,121,131,133,215,216,217,218,220,226,228,229,230],[78,120,121,133,215,216,217,218,220,226,228,229,230],[78,121,133,134,135,152,163,215,216,217,218,220,226,228,229,230],[78,121,133,134,135,148,152,155,215,216,217,218,220,226,227,228,229,230],[78,116,121,215,216,217,218,220,226,228,229,230],[78,121,129,133,136,141,152,163,215,216,217,218,220,226,228,229,230],[78,121,133,134,136,137,141,152,160,163,215,216,217,218,220,226,228,229,230],[78,121,136,138,152,160,163,215,216,217,218,220,226,228,229,230],[76,77,78,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,215,216,217,218,220,226,228,229,230],[78,121,133,139,215,216,217,218,220,226,228,229,230],[78,121,140,163,168,215,216,217,218,220,226,228,229,230],[78,121,129,133,141,152,215,216,217,218,220,226,228,229,230],[78,121,142,215,216,217,218,220,226,228,229,230],[78,121,143,215,216,217,218,220,226,228,229,230],[78,120,121,144,215,216,217,218,220,226,228,229,230],[78,118,119,120,121,122,123,124,125,126,127,128,129,130,131,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,215,216,217,218,220,226,227,228,229,230],[78,121,146,215,216,217,218,220,226,228,229,230],[78,121,147,215,216,217,218,220,226,228,229,230],[78,121,133,148,149,215,216,217,218,220,226,228,229,230],[78,121,148,150,164,166,215,216,217,218,220,226,228,229,230],[78,121,133,152,153,155,215,216,217,218,220,226,228,229,230],[78,121,154,155,215,216,217,218,220,226,228,229,230],[78,121,152,153,215,216,217,218,220,226,228,229,230],[78,121,155,215,216,217,218,220,226,228,229,230],[78,121,156,215,216,217,218,220,226,228,229,230],[78,118,121,152,215,216,217,218,220,226,228,229,230],[78,121,133,158,159,215,216,217,218,220,226,228,229,230],[78,121,158,159,215,216,217,218,220,226,228,229,230],[78,121,126,141,152,160,215,216,217,218,220,226,227,228,229,230],[78,121,161,215,216,217,218,220,226,228,229,230],[78,121,141,162,215,216,217,218,220,226,228,229,230],[78,121,136,147,163,215,216,217,218,220,226,228,229,230],[78,121,126,164,215,216,217,218,220,226,228,229,230],[78,121,152,165,215,216,217,218,220,226,228,229,230],[78,121,140,166,215,216,217,218,220,226,228,229,230],[78,121,167,215,216,217,218,220,226,228,229,230],[78,121,133,135,144,152,155,163,166,168,215,216,217,218,220,226,228,229,230],[78,121,152,169,215,216,217,218,220,226,228,229,230],[78,121,215,216,217,218,220,226,228,229,230,301],[78,121,215,216,217,218,220,226,228,229,230,299,300],[78,121,215,216,217,218,220,226,228,229,230,303,342],[78,121,215,216,217,218,220,226,228,229,230,303,327,342],[78,121,215,216,217,218,220,226,228,229,230,342],[78,121,215,216,217,218,220,226,228,229,230,303],[78,121,215,216,217,218,220,226,228,229,230,303,328,342],[78,121,215,216,217,218,220,226,228,229,230,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341],[78,121,215,216,217,218,220,226,228,229,230,328,342],[78,121,134,152,170,215,216,217,218,220,226,228,229,230,234],[78,121,136,170,215,216,217,218,220,226,228,229,230,235,239],[78,121,133,136,138,141,152,160,163,169,170,215,216,217,218,220,226,227,228,229,230],[78,121,215,216,217,218,220,226,228,229,230,346],[78,121,133,213,215,216,217,218,220,226,228,229,230],[78,116,121,126,134,136,160,164,168,215,216,217,220,221,226,227,228,229,230],[78,121,215,216,217,218,220,226,229,230],[78,121,215,216,217,218,226,228,229,230],[78,116,121,215,216,218,220,226,228,229,230],[78,121,126,144,152,155,160,164,168,216,217,218,220,226,228,229,230],[78,121,170,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231],[78,121,126,134,135,142,160,169,215,216,217,218,220,226,228,229,230],[78,121,215,216,217,218,220,226,228,230],[78,121,134,215,217,218,220,226,228,229,230],[78,121,215,216,217,218,220,226,228,229],[78,121,215,216,217,218,220,228,229,230],[78,121,215,216,217,218,220,226,228,229,230,253,256,257,258],[78,121,215,216,217,218,220,226,228,229,230,253,256,257],[78,121,215,216,217,218,220,226,228,229,230,253,256],[78,121,122,215,216,217,218,220,226,228,229,230,253,254,255,258],[78,121,163,170,215,216,217,218,220,226,228,229,230],[64,78,121,215,216,217,218,220,226,228,229,230],[64,65,66,67,68,69,70,78,121,215,216,217,218,220,226,228,229,230],[78,121,215,216,217,218,220,226,228,229,230,275,281],[78,121,129,170,197,204,205,215,216,217,218,220,226,228,229,230],[78,121,133,170,192,193,194,196,197,205,206,211,215,216,217,218,220,226,228,229,230],[78,121,129,170,215,216,217,218,220,226,228,229,230],[78,121,170,192,215,216,217,218,220,226,228,229,230],[78,121,192,215,216,217,218,220,226,228,229,230],[78,121,198,215,216,217,218,220,226,228,229,230],[78,121,133,160,170,192,198,200,201,206,215,216,217,218,220,226,227,228,229,230],[78,121,200,215,216,217,218,220,226,228,229,230],[78,121,204,215,216,217,218,220,226,228,229,230],[78,121,141,160,170,192,198,215,216,217,218,220,226,227,228,229,230],[78,121,133,170,192,208,209,215,216,217,218,220,226,228,229,230],[78,121,192,193,194,195,198,202,203,204,205,206,207,211,212,215,216,217,218,220,226,228,229,230],[78,121,193,197,207,211,215,216,217,218,220,226,228,229,230],[78,121,133,170,192,193,194,196,197,204,207,208,210,215,216,217,218,220,226,228,229,230],[78,121,197,199,202,203,215,216,217,218,220,226,228,229,230],[78,121,152,170,215,216,217,218,220,226,228,229,230],[78,121,193,215,216,217,218,220,226,228,229,230],[78,121,195,215,216,217,218,220,226,228,229,230],[78,121,141,160,170,215,216,217,218,220,226,227,228,229,230],[78,121,192,193,195,215,216,217,218,220,226,228,229,230],[78,121,215,216,217,218,220,226,228,229,230,279],[78,121,215,216,217,218,220,226,228,229,230,276,280],[78,121,215,216,217,218,220,226,228,229,230,278],[78,88,92,121,163,215,216,217,218,220,226,228,229,230],[78,88,121,152,163,215,216,217,218,220,226,228,229,230],[78,83,121,215,216,217,218,220,226,228,229,230],[78,85,88,121,160,163,215,216,217,218,220,226,227,228,229,230],[78,121,141,160,215,216,217,218,220,226,227,228,229,230],[78,121,170,215,216,217,218,220,226,228,229,230],[78,83,121,170,215,216,217,218,220,226,228,229,230],[78,85,88,121,141,163,215,216,217,218,220,226,228,229,230],[78,80,81,84,87,121,133,152,163,215,216,217,218,220,226,228,229,230],[78,88,95,121,215,216,217,218,220,226,228,229,230],[78,80,86,121,215,216,217,218,220,226,228,229,230],[78,88,109,110,121,215,216,217,218,220,226,228,229,230],[78,84,88,121,155,163,170,215,216,217,218,220,226,228,229,230],[78,109,121,170,215,216,217,218,220,226,228,229,230],[78,82,83,121,170,215,216,217,218,220,226,228,229,230],[78,88,121,215,216,217,218,220,226,228,229,230],[78,82,83,84,85,86,87,88,89,90,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,110,111,112,113,114,115,121,215,216,217,218,220,226,228,229,230],[78,88,103,121,215,216,217,218,220,226,228,229,230],[78,88,95,96,121,215,216,217,218,220,226,228,229,230],[78,86,88,96,97,121,215,216,217,218,220,226,228,229,230],[78,87,121,215,216,217,218,220,226,228,229,230],[78,80,83,88,121,215,216,217,218,220,226,228,229,230],[78,88,92,96,97,121,215,216,217,218,220,226,228,229,230],[78,92,121,215,216,217,218,220,226,228,229,230],[78,86,88,91,121,163,215,216,217,218,220,226,228,229,230],[78,80,85,88,95,121,215,216,217,218,220,226,228,229,230],[78,121,152,215,216,217,218,220,226,228,229,230],[78,83,88,109,121,168,170,215,216,217,218,220,226,228,229,230]],"fileInfos":[{"version":"69684132aeb9b5642cbcd9e22dff7818ff0ee1aa831728af0ecf97d3364d5546","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"092c2bfe125ce69dbb1223c85d68d4d2397d7d8411867b5cc03cec902c233763","affectsGlobalScope":true,"impliedFormat":1},{"version":"07f073f19d67f74d732b1adea08e1dc66b1b58d77cb5b43931dee3d798a2fd53","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7a3c8b952931daebdfc7a2897c53c0a1c73624593fa070e46bd537e64dcd20a","affectsGlobalScope":true,"impliedFormat":1},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","affectsGlobalScope":true,"impliedFormat":1},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"936e80ad36a2ee83fc3caf008e7c4c5afe45b3cf3d5c24408f039c1d47bdc1df","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"fef8cfad2e2dc5f5b3d97a6f4f2e92848eb1b88e897bb7318cef0e2820bceaab","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"b5ce7a470bc3628408429040c4e3a53a27755022a32fd05e2cb694e7015386c7","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"3cbad9a1ba4453443026ed38e4b8be018abb26565fa7c944376463ad9df07c41","impliedFormat":1},{"version":"9f2ea5a871cb5ca0aac987e2b63e268b7039640bd243f087e36029f79b738091","impliedFormat":1},{"version":"283727a253870c4e0f7260bad309bc66735df3ebfbe5c12df11d27c41a2f1cd0","impliedFormat":1},{"version":"e9e3527ca63cf5d82a0c3cc14083c772bfff71da0476b81662eb2246e8871da0","impliedFormat":1},{"version":"71f05a5c741ddcd9d1fb9ff21451774b130d02d03ec3130e76b76f8ea75628bf","impliedFormat":1},{"version":"f5dd90ec32c1ecb66a7d05fa50bda6b0c0d615445d1aa758818bff7a9fe5e6b3","impliedFormat":1},{"version":"d969c988d9e02ec9e5332d85aae9ee56ba55fd658a628364607b764388b045fa","impliedFormat":1},{"version":"96cca42aa03c19e96be5cc421ade9ee28dead1f7d853c0cfd37dad8918ab1bca","impliedFormat":1},{"version":"d42058682fa8a074f02ae46e7dcc7a97cf49f9d475841c5d510e31b5abfa40ff","impliedFormat":1},{"version":"fed3925adfdbb4f4c394d184aa753e5ed1fdab264bab5f1915240c966555b878","signature":"b83665bea5eb84f77598635075f9708b3816fceca1bcab492d3645149b48d818"},{"version":"70521b6ab0dcba37539e5303104f29b721bfb2940b2776da4cc818c07e1fefc1","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"a79e62f1e20467e11a904399b8b18b18c0c6eea6b50c1168bf215356d5bebfaf","affectsGlobalScope":true,"impliedFormat":1},{"version":"49a5a44f2e68241a1d2bd9ec894535797998841c09729e506a7cbfcaa40f2180","affectsGlobalScope":true,"impliedFormat":1},{"version":"4967529644e391115ca5592184d4b63980569adf60ee685f968fd59ab1557188","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"24bd580b5743dc56402c440dc7f9a4f5d592ad7a419f25414d37a7bfe11e342b","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"6bdc71028db658243775263e93a7db2fd2abfce3ca569c3cca5aee6ed5eb186d","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"4d2b0eb911816f66abe4970898f97a2cfc902bcd743cbfa5017fad79f7ef90d8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"e53a3c2a9f624d90f24bf4588aacd223e7bec1b9d0d479b68d2f4a9e6011147f","impliedFormat":1},{"version":"24b8685c62562f5d98615c5a0c1d05f297cf5065f15246edfe99e81ec4c0e011","impliedFormat":1},{"version":"93507c745e8f29090efb99399c3f77bec07db17acd75634249dc92f961573387","impliedFormat":1},{"version":"339dc5265ee5ed92e536a93a04c4ebbc2128f45eeec6ed29f379e0085283542c","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b8582f8bf95b9b901bf6cf47b9ee3560c7f340be0bd39cb432f21e9e136c36a7","affectsGlobalScope":true,"impliedFormat":1},{"version":"6b042aa5d277ad6963e2837179fd2f8fbb01968ac67115b0833c0244e93d1d50","impliedFormat":1},{"version":"7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419","impliedFormat":1},{"version":"8c0bcd6c6b67b4b503c11e91a1fb91522ed585900eab2ab1f61bba7d7caa9d6f","impliedFormat":1},{"version":"9e025aa38cad40827cc30aca974fe33fe2c4652fe8c88f48dadbbbd6300c8b07","affectsGlobalScope":true,"impliedFormat":1},{"version":"f3e58c4c18a031cbb17abec7a4ad0bd5ae9fc70c1f4ba1e7fb921ad87c504aca","impliedFormat":1},{"version":"84c1930e33d1bb12ad01bcbe11d656f9646bd21b2fb2afd96e8e10615a021aef","impliedFormat":1},{"version":"35ec8b6760fd7138bbf5809b84551e31028fb2ba7b6dc91d95d098bf212ca8b4","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"4b87f767c7bc841511113c876a6b8bf1fd0cb0b718c888ad84478b372ec486b1","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d04e3640dd9eb67f7f1e5bd3d0bf96c784666f7aefc8ac1537af6f2d38d4c29","impliedFormat":1},{"version":"9d19808c8c291a9010a6c788e8532a2da70f811adb431c97520803e0ec649991","impliedFormat":1},{"version":"5a369483ac4cfbdf0331c248deeb36140e6907db5e1daed241546b4a2055f82c","impliedFormat":1},{"version":"e8f5b5cc36615c17d330eaf8eebbc0d6bdd942c25991f96ef122f246f4ff722f","impliedFormat":1},{"version":"f0bd7e6d931657b59605c44112eaf8b980ba7f957a5051ed21cb93d978cf2f45","impliedFormat":1},{"version":"71450bbc2d82821d24ca05699a533e72758964e9852062c53b30f31c36978ab8","affectsGlobalScope":true,"impliedFormat":1},{"version":"0ada07543808f3b967624645a8e1ccd446f8b01ade47842acf1328aec899fed0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4c21aaa8257d7950a5b75a251d9075b6a371208fc948c9c8402f6690ef3b5b55","impliedFormat":1},{"version":"b5895e6353a5d708f55d8685c38a235c3a6d8138e374dee8ceb8ffde5aa8002a","impliedFormat":1},{"version":"5b75ca915164e4a7ad94a60729fe45b8a62e7750ab232d0122f8ccdd768f5314","impliedFormat":1},{"version":"93bd413918fa921c8729cef45302b24d8b6c7855d72d5bf82d3972595ae8dcbf","impliedFormat":1},{"version":"4ff41188773cbf465807dd2f7059c7494cbee5115608efc297383832a1150c43","impliedFormat":1},{"version":"dccdf1677e531e33f8ac961a68bc537418c9a414797c1ea7e91307501cdc3f5e","impliedFormat":1},{"version":"1354ca5c38bd3fd3836a68e0f7c9f91f172582ba30ab15bb8c075891b91502b7","affectsGlobalScope":true,"impliedFormat":1},{"version":"5155da3047ef977944d791a2188ff6e6c225f6975cc1910ab7bb6838ab84cede","impliedFormat":1},{"version":"93f437e1398a4f06a984f441f7fa7a9f0535c04399619b5c22e0b87bdee182cb","impliedFormat":1},{"version":"afbe24ab0d74694372baa632ecb28bb375be53f3be53f9b07ecd7fc994907de5","impliedFormat":1},{"version":"3e5b3163e34f3dc24cba59db4bb90bcc33555cccac06b707501439bdcf3d4df4","affectsGlobalScope":true,"impliedFormat":1},{"version":"6b19db3600a17af69d4f33d08cc7076a7d19fb65bb36e442cac58929ec7c9482","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e043a1bc8fbf2a255bccf9bf27e0f1caf916c3b0518ea34aa72357c0afd42ec","impliedFormat":1},{"version":"b4f70ec656a11d570e1a9edce07d118cd58d9760239e2ece99306ee9dfe61d02","impliedFormat":1},{"version":"3bc2f1e2c95c04048212c569ed38e338873f6a8593930cf5a7ef24ffb38fc3b6","impliedFormat":1},{"version":"8145e07aad6da5f23f2fcd8c8e4c5c13fb26ee986a79d03b0829b8fce152d8b2","impliedFormat":1},{"version":"f9d9d753d430ed050dc1bf2667a1bab711ccbb1c1507183d794cc195a5b085cc","impliedFormat":1},{"version":"9eece5e586312581ccd106d4853e861aaaa1a39f8e3ea672b8c3847eedd12f6e","impliedFormat":1},{"version":"5b6844ad931dcc1d3aca53268f4bd671428421464b1286746027aede398094f2","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"125d792ec6c0c0f657d758055c494301cc5fdb327d9d9d5960b3f129aff76093","impliedFormat":1},{"version":"0dbcebe2126d03936c70545e96a6e41007cf065be38a1ce4d32a39fcedefead4","affectsGlobalScope":true,"impliedFormat":1},{"version":"1851a3b4db78664f83901bb9cac9e45e03a37bb5933cc5bf37e10bb7e91ab4eb","impliedFormat":1},{"version":"09d479208911ac3ac6a7c2fe86217fc1abe6c4f04e2d52e4890e500699eeab32","affectsGlobalScope":true,"impliedFormat":1},{"version":"12ed4559eba17cd977aa0db658d25c4047067444b51acfdcbf38470630642b23","affectsGlobalScope":true,"impliedFormat":1},{"version":"f3ffabc95802521e1e4bcba4c88d8615176dc6e09111d920c7a213bdda6e1d65","impliedFormat":1},{"version":"c40b3d3cfbb1227c8935f681c2480a32b560e387dd771d329cdbd1641f2d6da5","impliedFormat":1},{"version":"ae56f65caf3be91108707bd8dfbccc2a57a91feb5daabf7165a06a945545ed26","impliedFormat":1},{"version":"a136d5de521da20f31631a0a96bf712370779d1c05b7015d7019a9b2a0446ca9","impliedFormat":1},{"version":"5b566927cad2ed2139655d55d690ffa87df378b956e7fe1c96024c4d9f75c4cf","affectsGlobalScope":true,"impliedFormat":1},{"version":"c4a3720550d1787c8d6284040853c0781ff1e2cd8d842f2cb44547525ee34c36","affectsGlobalScope":true,"impliedFormat":1},{"version":"d3dffd70e6375b872f0b4e152de4ae682d762c61a24881ecc5eb9f04c5caf76f","impliedFormat":1},{"version":"f008d63ce0077f533e39df44b82d660707b15b0f8e31fbc153a62bb00b99bfe5","impliedFormat":1},{"version":"d91a7d8b5655c42986f1bdfe2105c4408f472831c8f20cf11a8c3345b6b56c8c","impliedFormat":1},{"version":"6bdb3144f8bf020f513651a6ea1cb8a378a612c0791042e0436fd9adf7372a17","affectsGlobalScope":true,"impliedFormat":1},{"version":"e8a979b8af001c9fc2e774e7809d233c8ca955a28756f52ee5dee88ccb0611d2","impliedFormat":1},{"version":"cac793cc47c29e26e4ac3601dcb00b4435ebed26203485790e44f2ad8b6ad847","impliedFormat":1},{"version":"f634e4c7d5cdba8e092d98098033b311c8ef304038d815c63ffdb9f78f3f7bb7","impliedFormat":1},{"version":"fe975cdf0c898e54ed261077e9c3f86d6fe9b7bd3f5b127ff4415b3478dfa1c0","signature":"38d5dc55fb5cac9e6f149c1f7d367afa7d650659cd0d069b4d2d0a5103eac113"},{"version":"915fbfee7ff24d58d2a4e31d531ec4a9fa9260fa4bd496432d2e3fa26619adda","signature":"54f7a8979819f42a7266240eb6248afb35a858bfc2b52e0f2bca4592918440fd"},{"version":"7299681d533110182c06f591ed93891bc298141d33e99aca68d7b35218825bd6","signature":"ab9d3afcac9b93cd848c6709c4948b64110c66d6c8496e27d663255afe902f1a"},{"version":"121b3b8a0e4c777ac13f5f6a90c838f54e2bb91138a62236c45fb30d8bae1340","signature":"1d283b935bf8f0c9d737b19974315ed6540f812751023c425e94b69281c54ac8"},{"version":"96e6ac55d061bf88ab471cc1594d018c594d6319d506f74fcb2650dcf41f772e","signature":"c96462c6a68bc963c5cf08c2f9eba64373f880bdf51933a33623963ad7c9dfb9"},{"version":"6faeec51195ff98c2aa751694512106ab09aa6bff264ba127ce5e912d649d594","signature":"62a31033935c61a729ff02b81ad002ab7c87850abba6afb6dc6d7df4039fa62d"},{"version":"ed1f17bd89062fdf4a68a83ef6e5faebdca44c45fb6450fc752f004be4082646","signature":"c06ca9d833c2265dd194c95149b1f91068e002ac79963f8080fac5541e12d75c"},{"version":"864a2fe06ac418cd8a5ee8d1318ad6f67fad5f0632b8c7b8116df826382d4154","signature":"e259b61fe14a0c0d5552eb17a2b54c93c79525d038df6e354fa516bdc07f1784"},{"version":"d99458db9a126122f67ed63a0d683e43c634a629949605d5107776d9b516a6f3","signature":"baceb3188a7d82e14d5847a4780c1c514c7374279a26a935e2d5d51e6efeaaa9"},{"version":"0f441c69881e4ad307b9675b8cc8ebb476d2bd9f3ab26db981ad6878257b8e62","signature":"7027ccb8e6638acd8f2983ccacadff1230fb9fa5e97378e8b83c95dcbecf5db9"},{"version":"e0560594755811f3d7ee77fa62d16c9be66d7f92cd46acfc7bb7c2df43aca1a4","signature":"eb52af7a722c9f79df66918ad590891b566e63b2b5fb869e549c07a08421b4f9"},{"version":"12bc17b00f9e2c895e9ae3756ffa6ef51ec053c62ce889c3e32c953ebe997d97","signature":"5cda7cd6fe7f0f8f7df890cb36e7f274a11136e5a1ff0ef347f17c60fb2c5ab5"},{"version":"d88b3dc8b7055665059ea06ffafce9467fc4bdfa7cb2d7a6f4262556bb482b0d","impliedFormat":1},{"version":"b6d03c9cfe2cf0ba4c673c209fcd7c46c815b2619fd2aad59fc4229aaef2ed43","impliedFormat":1},{"version":"32ddc6ad753ae79571bbf28cebff7a383bf7f562ac5ef5d25c94ef7f71609d49","impliedFormat":1},{"version":"670a76db379b27c8ff42f1ba927828a22862e2ab0b0908e38b671f0e912cc5ed","impliedFormat":1},{"version":"81df92841a7a12d551fcbc7e4e83dbb7d54e0c73f33a82162d13e9ae89700079","impliedFormat":1},{"version":"069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9","impliedFormat":1},{"version":"104c67f0da1bdf0d94865419247e20eded83ce7f9911a1aa75fc675c077ca66e","impliedFormat":1},{"version":"cc0d0b339f31ce0ab3b7a5b714d8e578ce698f1e13d7f8c60bfb766baeb1d35c","impliedFormat":1},{"version":"332680a9475bd631519399f9796c59502aa499aa6f6771734eec82fa40c6d654","impliedFormat":1},{"version":"911484710eb1feaf615cb68eb5875cbfb8edab2a032f0e4fe5a7f8b17e3a997c","impliedFormat":1},{"version":"d83f3c0362467589b3a65d3a83088c068099c665a39061bf9b477f16708fa0f9","impliedFormat":1},{"version":"4fc05cd35f313ea6bc2cd52bfd0d3d1a79c894aeaeffd7c285153cb7d243f19b","impliedFormat":1},{"version":"29994a97447d10d003957bcc0c9355c272d8cf0f97143eb1ade331676e860945","impliedFormat":1},{"version":"6865b4ef724cb739f8f1511295f7ce77c52c67ff4af27e07b61471d81de8ecfc","impliedFormat":1},{"version":"9cddf06f2bc6753a8628670a737754b5c7e93e2cfe982a300a0b43cf98a7d032","impliedFormat":1},{"version":"3f8e68bd94e82fe4362553aa03030fcf94c381716ce3599d242535b0d9953e49","impliedFormat":1},{"version":"63e628515ec7017458620e1624c594c9bd76382f606890c8eebf2532bcab3b7c","impliedFormat":1},{"version":"355d5e2ba58012bc059e347a70aa8b72d18d82f0c3491e9660adaf852648f032","impliedFormat":1},{"version":"0c543e751bbd130170ed4efdeca5ff681d06a99f70b5d6fe7defad449d08023d","impliedFormat":1},{"version":"c301dded041994ed4899a7cf08d1d6261a94788da88a4318c1c2338512431a03","impliedFormat":1},{"version":"192be331d8be6eed03af9b0ee83c21e043c7ca122f111282b1b1bdb98f2a7535","impliedFormat":1},{"version":"ded3d0fb8ac3980ae7edcc723cc2ad35da1798d52cceff51c92abe320432ceeb","impliedFormat":1},{"version":"ed7f0e3731c834809151344a4c79d1c4935bf9bc1bd0a9cc95c2f110b1079983","impliedFormat":1},{"version":"d4886d79f777442ac1085c7a4fe421f2f417aa70e82f586ca6979473856d0b09","impliedFormat":1},{"version":"ed849d616865076f44a41c87f27698f7cdf230290c44bafc71d7c2bc6919b202","impliedFormat":1},{"version":"9a0a0af04065ddfecc29d2b090659fce57f46f64c7a04a9ba63835ef2b2d0efa","impliedFormat":1},{"version":"10297d22a9209a718b9883a384db19249b206a0897e95f2b9afeed3144601cb0","impliedFormat":1},{"version":"8e335bc47365e92f689795a283c77b4b8d4d9c42c5d607d1327f88c876e4e85d","impliedFormat":1},{"version":"34d206f6ba993e601dade2791944bdf742ab0f7a8caccc661106c87438f4f904","impliedFormat":1},{"version":"05ca49cc7ba9111f6c816ecfadb9305fffeb579840961ee8286cc89749f06ebd","impliedFormat":1},{"version":"2151db9166dfd90feaa67f0c3a07efcab39e1640f1b26abc81632d8e1bf95fcb","impliedFormat":1},{"version":"309f3a357cc08760a602bd9b1177d4474426e6e2897a7295c898920198d968fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"ee31fab138939ef1903831933d524de9944511759778eedaaed56d6eb7f8697d","impliedFormat":1},{"version":"e45cc72cc9e7ad726ec83141fa4cd221d432062de34586ff107a0442ae28bf19","impliedFormat":1},{"version":"860390219b49e0a99244614a4b23eb4b23c4c057a171bbffdce51d5afd2da69a","impliedFormat":1},{"version":"3083591fd0a77addd337b02f9fcf0d4f009e41c79fa42f862d6fcf76f3fceb48","impliedFormat":1},{"version":"34810cb47e6bee7cd4bad2f174793f5926ba5889c5d180e29b02c1871a820476","affectsGlobalScope":true,"impliedFormat":1},{"version":"7115f1157a00937d712e042a011eb85e9d80b13eff78bac5f210ee852f96879d","impliedFormat":1},{"version":"0ac74c7586880e26b6a599c710b59284a284e084a2bbc82cd40fb3fbfdea71ae","affectsGlobalScope":true,"impliedFormat":1},{"version":"2ce12357dadbb8efc4e4ec4dab709c8071bf992722fc9adfea2fe0bd5b50923f","impliedFormat":1},{"version":"d021d871089c519030a098fa3d32f5d95059b699b0fc6c45c85a96f92cae233c","impliedFormat":1},{"version":"170191e78d839545486d90264cdef12960f27928e9493fe1fe91a8f068d24c74","impliedFormat":1},{"version":"b05b9ef20d18697e468c3ae9cecfff3f47e8976f9522d067047e3f236db06a41","affectsGlobalScope":true,"impliedFormat":1},{"version":"eec5e9a5629f6740aac21e49783a373a3767770ad559cd41285ebbb0db39a4a2","affectsGlobalScope":true,"impliedFormat":1},{"version":"1745f0b1ab53f414b4f8ebb2c6a902fda28d40f454edac8e92b4d7c974a2051c","affectsGlobalScope":true,"impliedFormat":1},{"version":"403f9ee3b18e550bccb23b0ef67ba61e4990a902f67db1afae1ad23602fef9d1","impliedFormat":1},{"version":"1a7a729938558fe198d979d3f53dece9c9112124b7b081a7fa0adcc98bf15fd8","impliedFormat":1},{"version":"067f76ab5254b1bdfc94154730b7a30c12e3aad8b9d04ec62c0d6b7a1f40ea0e","affectsGlobalScope":true,"impliedFormat":1},{"version":"f67f24b0d972d7d0f52a4e2f4f8ffd5cd786cb411044693026731918df935371","affectsGlobalScope":true,"impliedFormat":1},{"version":"37be812b06e518320ba82e2aff3ac2ca37370a9df917db708f081b9043fa3315","impliedFormat":1},{"version":"d3f2d715f57df3f04bf7b16dde01dec10366f64fce44503c92b8f78f614c1769","impliedFormat":1},{"version":"b78cd10245a90e27e62d0558564f5d9a16576294eee724a59ae21b91f9269e4a","impliedFormat":1},{"version":"baac9896d29bcc55391d769e408ff400d61273d832dd500f21de766205255acb","impliedFormat":1},{"version":"2f5747b1508ccf83fad0c251ba1e5da2f5a30b78b09ffa1cfaf633045160afed","impliedFormat":1},{"version":"86ea91bfa7fef1eeb958056f30f1db4e0680bc9b5132e5e9d6e9cfd773c0c4fd","affectsGlobalScope":true,"impliedFormat":1},{"version":"b71c603a539078a5e3a039b20f2b0a0d1708967530cf97dec8850a9ca45baa2b","impliedFormat":1},{"version":"0e13570a7e86c6d83dd92e81758a930f63747483e2cd34ef36fcdb47d1f9726a","impliedFormat":1},{"version":"d26a79f97f25eb1c5fc36a8552e4decc7ad11104a016d31b1307c3afaf48feb1","impliedFormat":1},{"version":"dbd0794f86b0f3e7c2c28bbe6cbf91adc6ef2203c6a832548ef199816d47039c","affectsGlobalScope":true,"impliedFormat":1},{"version":"25be1eb939c9c63242c7a45446edb20c40541da967f43f1aa6a00ed53c0552db","impliedFormat":1},{"version":"5339f84dfcb7b04aa1c2b4d7713d6128039381447f07abc2e48d36685e2eef44","impliedFormat":1},{"version":"fb35a61a39c933d31b5b2549d906b2c932a1486622958586f662dbd4b2fe72e6","impliedFormat":1},{"version":"24e2728268be1ad2407bab004549d2753a49b2acb0f117a04c4e28ffb3ecdd4f","impliedFormat":1},{"version":"aff159b14eba59afe98a88fe6f57881ba02895fb9763512dda9083497bdcd0e6","impliedFormat":1},{"version":"b6bc775d112a7761a50594fc589aeaa8893c139ffe3db2b4999756e17f367a8d","impliedFormat":1},{"version":"0b8f398b88a43f8bf29a50920e7ddef19c06c3008b351e7047e9613d7195c638","impliedFormat":1},{"version":"25d0e0fe3731bc85c7bd2ef7f7e1faf4f5201be1c10ff3a19e1afa6ec4568669","impliedFormat":1},{"version":"26080058b725ac0b480241751255b4391f722263778e84e66a62068705aafd3c","impliedFormat":1},{"version":"46afbf46c3d62eac2afead3a2011d506637bf4f2c05e1fd64bbf7e2bb2947b7c","impliedFormat":1},{"version":"02f634f868780eaaff5e2d3fb4570dac8e7f018a8650bb9a0ac1deb4915df8d1","impliedFormat":1},{"version":"991cf4ed946cdf4c140ccaad45c61fc36a25b238a8fa95af51e93cb20c4b0503","impliedFormat":1},{"version":"0f17f5f14a5f53e5709404b5b59fe816eaad15a469412b73330e6f69834234e0","impliedFormat":1},{"version":"efe194e4e6bdc09be4757106d6b0640c43094b719e9e77ba16b4db47f7a9c7a8","impliedFormat":1},{"version":"316fdd0612da3236b1819b86c33b18876848a1af28b8bd7b707d2dab585b604d","impliedFormat":1},{"version":"d20d95759862940b16e438459878555ba4c4e76661cba00f618ee5cecc83661d","impliedFormat":1},{"version":"99b404de29efde207e00eeea06941c1cc1ba10096745834e5667c927acaa085d","impliedFormat":1},{"version":"e0c868a08451c879984ccf4d4e3c1240b3be15af8988d230214977a3a3dad4ce","impliedFormat":1},{"version":"6fc1a4f64372593767a9b7b774e9b3b92bf04e8785c3f9ea98973aa9f4bbe490","impliedFormat":1},{"version":"ff09b6fbdcf74d8af4e131b8866925c5e18d225540b9b19ce9485ca93e574d84","impliedFormat":1},{"version":"d5895252efa27a50f134a9b580aa61f7def5ab73d0a8071f9b5bf9a317c01c2d","impliedFormat":1},{"version":"2c378d9368abcd2eba8c29b294d40909845f68557bc0b38117e4f04fc56e5f9c","impliedFormat":1},{"version":"56208c500dcb5f42be7e18e8cb578f257a1a89b94b3280c506818fed06391805","impliedFormat":1},{"version":"0c94c2e497e1b9bcfda66aea239d5d36cd980d12a6d9d59e66f4be1fa3da5d5a","impliedFormat":1},{"version":"bb220eaac1677e2ad82ac4e7fd3e609a0c7b6f2d6d9c673a35068c97f9fcd5cd","affectsGlobalScope":true,"impliedFormat":1},{"version":"1f366bde16e0513fa7b64f87f86689c4d36efd85afce7eb24753e9c99b91c319","impliedFormat":1},{"version":"e2b48abff5a8adc6bb1cd13a702b9ef05e6045a98e7cfa95a8779b53b6d0e69d","impliedFormat":1},{"version":"afe73051ff6a03a9565cbd8ebb0e956ee3df5e913ad5c1ded64218aabfa3dcb5","impliedFormat":1},{"version":"035a5df183489c2e22f3cf59fc1ed2b043d27f357eecc0eb8d8e840059d44245","impliedFormat":1},{"version":"a4809f4d92317535e6b22b01019437030077a76fec1d93b9881c9ed4738fcc54","impliedFormat":1},{"version":"5f53fa0bd22096d2a78533f94e02c899143b8f0f9891a46965294ee8b91a9434","impliedFormat":1},{"version":"2991f75d2fc50894104661c7c8a9619d98d94a56bce019ed31c41f8f6d63de9d","affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcc132f207d097d7d3aa75615ab9a2e71d6a478162dde8b67f88ea19f3e54de","impliedFormat":1},{"version":"0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","impliedFormat":1},{"version":"c085e9aa62d1ae1375794c1fb927a445fa105fed891a7e24edbb1c3300f7384a","impliedFormat":1},{"version":"f315e1e65a1f80992f0509e84e4ae2df15ecd9ef73df975f7c98813b71e4c8da","impliedFormat":1},{"version":"5b9586e9b0b6322e5bfbd2c29bd3b8e21ab9d871f82346cb71020e3d84bae73e","impliedFormat":1},{"version":"3e70a7e67c2cb16f8cd49097360c0309fe9d1e3210ff9222e9dac1f8df9d4fb6","impliedFormat":1},{"version":"ab68d2a3e3e8767c3fba8f80de099a1cfc18c0de79e42cb02ae66e22dfe14a66","impliedFormat":1},{"version":"d96cc6598148bf1a98fb2e8dcf01c63a4b3558bdaec6ef35e087fd0562eb40ec","impliedFormat":1},{"version":"f8db4fea512ab759b2223b90ecbbe7dae919c02f8ce95ec03f7fb1cf757cfbeb","affectsGlobalScope":true,"impliedFormat":1},{"version":"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","impliedFormat":1},{"version":"7220461ab7f6d600b313ce621346c315c3a0ebc65b5c6f268488c5c55b68d319","impliedFormat":1},{"version":"b14c272987c82d49f0f12184c9d8d07a7f71767be99cb76faa125b777c70e962","impliedFormat":1},{"version":"fcf79300e5257a23ed3bacaa6861d7c645139c6f7ece134d15e6669447e5e6db","impliedFormat":1},{"version":"187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42","impliedFormat":1},{"version":"aa2c18a1b5a086bbcaae10a4efba409cc95ba7287d8cf8f2591b53704fea3dea","impliedFormat":1},{"version":"b88749bdb18fc1398370e33aa72bc4f88274118f4960e61ce26605f9b33c5ba2","impliedFormat":1},{"version":"0aaef8cded245bf5036a7a40b65622dd6c4da71f7a35343112edbe112b348a1e","impliedFormat":1},{"version":"00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a","impliedFormat":1},{"version":"a873c50d3e47c21aa09fbe1e2023d9a44efb07cc0cb8c72f418bf301b0771fd3","impliedFormat":1},{"version":"7c14ccd2eaa82619fffc1bfa877eb68a012e9fb723d07ee98db451fadb618906","impliedFormat":1},{"version":"49c36529ee09ea9ce19525af5bb84985ea8e782cb7ee8c493d9e36d027a3d019","impliedFormat":1},{"version":"df996e25faa505f85aeb294d15ebe61b399cf1d1e49959cdfaf2cc0815c203f9","impliedFormat":1},{"version":"4f6a12044ee6f458db11964153830abbc499e73d065c51c329ec97407f4b13dd","impliedFormat":1},{"version":"3444e1ba06fe73df6673e38d6421613467cd5d728068d7c0351df80872d3484d","impliedFormat":1},{"version":"170d4db14678c68178ee8a3d5a990d5afb759ecb6ec44dbd885c50f6da6204f6","affectsGlobalScope":true,"impliedFormat":1},{"version":"8a8eb4ebffd85e589a1cc7c178e291626c359543403d58c9cd22b81fab5b1fb9","impliedFormat":1},{"version":"9e83685e23baf56b50eab5f89bcc46c66ccd709c4a44d32e635040196ad96603","impliedFormat":1},{"version":"a0acca63c9e39580f32a10945df231815f0fe554c074da96ba6564010ffbd2d8","impliedFormat":1},{"version":"cf3d384d082b933d987c4e2fe7bfb8710adfd9dc8155190056ed6695a25a559e","impliedFormat":1},{"version":"9871b7ee672bc16c78833bdab3052615834b08375cb144e4d2cba74473f4a589","impliedFormat":1},{"version":"c863198dae89420f3c552b5a03da6ed6d0acfa3807a64772b895db624b0de707","impliedFormat":1},{"version":"8b03a5e327d7db67112ebbc93b4f744133eda2c1743dbb0a990c61a8007823ef","impliedFormat":1},{"version":"86c73f2ee1752bac8eeeece234fd05dfcf0637a4fbd8032e4f5f43102faa8eec","impliedFormat":1},{"version":"42fad1f540271e35ca37cecda12c4ce2eef27f0f5cf0f8dd761d723c744d3159","impliedFormat":1},{"version":"ff3743a5de32bee10906aff63d1de726f6a7fd6ee2da4b8229054dfa69de2c34","impliedFormat":1},{"version":"83acd370f7f84f203e71ebba33ba61b7f1291ca027d7f9a662c6307d74e4ac22","impliedFormat":1},{"version":"1445cec898f90bdd18b2949b9590b3c012f5b7e1804e6e329fb0fe053946d5ec","impliedFormat":1},{"version":"0e5318ec2275d8da858b541920d9306650ae6ac8012f0e872fe66eb50321a669","impliedFormat":1},{"version":"cf530297c3fb3a92ec9591dd4fa229d58b5981e45fe6702a0bd2bea53a5e59be","impliedFormat":1},{"version":"c1f6f7d08d42148ddfe164d36d7aba91f467dbcb3caa715966ff95f55048b3a4","impliedFormat":1},{"version":"f4e9bf9103191ef3b3612d3ec0044ca4044ca5be27711fe648ada06fad4bcc85","impliedFormat":1},{"version":"0c1ee27b8f6a00097c2d6d91a21ee4d096ab52c1e28350f6362542b55380059a","impliedFormat":1},{"version":"7677d5b0db9e020d3017720f853ba18f415219fb3a9597343b1b1012cfd699f7","impliedFormat":1},{"version":"bc1c6bc119c1784b1a2be6d9c47addec0d83ef0d52c8fbe1f14a51b4dfffc675","impliedFormat":1},{"version":"52cf2ce99c2a23de70225e252e9822a22b4e0adb82643ab0b710858810e00bf1","impliedFormat":1},{"version":"770625067bb27a20b9826255a8d47b6b5b0a2d3dfcbd21f89904c731f671ba77","impliedFormat":1},{"version":"d1ed6765f4d7906a05968fb5cd6d1db8afa14dbe512a4884e8ea5c0f5e142c80","impliedFormat":1},{"version":"799c0f1b07c092626cf1efd71d459997635911bb5f7fc1196efe449bba87e965","impliedFormat":1},{"version":"2a184e4462b9914a30b1b5c41cf80c6d3428f17b20d3afb711fff3f0644001fd","impliedFormat":1},{"version":"9eabde32a3aa5d80de34af2c2206cdc3ee094c6504a8d0c2d6d20c7c179503cc","impliedFormat":1},{"version":"397c8051b6cfcb48aa22656f0faca2553c5f56187262135162ee79d2b2f6c966","impliedFormat":1},{"version":"a8ead142e0c87dcd5dc130eba1f8eeed506b08952d905c47621dc2f583b1bff9","impliedFormat":1},{"version":"a02f10ea5f73130efca046429254a4e3c06b5475baecc8f7b99a0014731be8b3","impliedFormat":1},{"version":"c2576a4083232b0e2d9bd06875dd43d371dee2e090325a9eac0133fd5650c1cb","impliedFormat":1},{"version":"4c9a0564bb317349de6a24eb4efea8bb79898fa72ad63a1809165f5bd42970dd","impliedFormat":1},{"version":"f40ac11d8859092d20f953aae14ba967282c3bb056431a37fced1866ec7a2681","impliedFormat":1},{"version":"cc11e9e79d4746cc59e0e17473a59d6f104692fd0eeea1bdb2e206eabed83b03","impliedFormat":1},{"version":"b444a410d34fb5e98aa5ee2b381362044f4884652e8bc8a11c8fe14bbd85518e","impliedFormat":1},{"version":"c35808c1f5e16d2c571aa65067e3cb95afeff843b259ecfa2fc107a9519b5392","impliedFormat":1},{"version":"14d5dc055143e941c8743c6a21fa459f961cbc3deedf1bfe47b11587ca4b3ef5","impliedFormat":1},{"version":"a3ad4e1fc542751005267d50a6298e6765928c0c3a8dce1572f2ba6ca518661c","impliedFormat":1},{"version":"f237e7c97a3a89f4591afd49ecb3bd8d14f51a1c4adc8fcae3430febedff5eb6","impliedFormat":1},{"version":"3ffdfbec93b7aed71082af62b8c3e0cc71261cc68d796665faa1e91604fbae8f","impliedFormat":1},{"version":"662201f943ed45b1ad600d03a90dffe20841e725203ced8b708c91fcd7f9379a","impliedFormat":1},{"version":"c9ef74c64ed051ea5b958621e7fb853fe3b56e8787c1587aefc6ea988b3c7e79","impliedFormat":1},{"version":"2462ccfac5f3375794b861abaa81da380f1bbd9401de59ffa43119a0b644253d","impliedFormat":1},{"version":"34baf65cfee92f110d6653322e2120c2d368ee64b3c7981dff08ed105c4f19b0","impliedFormat":1},{"version":"844ab83672160ca57a2a2ea46da4c64200d8c18d4ebb2087819649cad099ff0e","impliedFormat":1},{"version":"ab82804a14454734010dcdcd43f564ff7b0389bee4c5692eec76ff5b30d4cf66","impliedFormat":1},{"version":"7d2b7fe4adb76d8253f20e4dbdce044f1cdfab4902ec33c3604585f553883f7d","impliedFormat":1},{"version":"1ba59c8bbeed2cb75b239bb12041582fa3e8ef32f8d0bd0ec802e38442d3f317","impliedFormat":1},{"version":"bae8d023ef6b23df7da26f51cea44321f95817c190342a36882e93b80d07a960","impliedFormat":1},{"version":"26a770cec4bd2e7dbba95c6e536390fffe83c6268b78974a93727903b515c4e7","impliedFormat":1}],"root":[72,[172,183]],"options":{"allowImportingTsExtensions":false,"allowSyntheticDefaultImports":true,"alwaysStrict":true,"composite":true,"declaration":true,"esModuleInterop":true,"module":99,"noImplicitAny":true,"noImplicitThis":true,"outDir":"./dist","rootDir":"./src","skipLibCheck":true,"sourceMap":true,"strict":true,"strictNullChecks":true,"target":9},"referencedMap":[[72,1],[172,2],[173,1],[174,1],[175,1],[182,3],[183,3],[180,1],[179,1],[177,1],[178,1],[176,1],[181,1],[186,4],[184,5],[275,5],[278,6],[277,5],[189,7],[185,4],[187,8],[188,4],[191,9],[233,10],[242,11],[190,12],[243,12],[260,5],[261,5],[262,5],[263,13],[264,5],[266,14],[267,15],[265,5],[268,5],[269,5],[238,16],[241,17],[270,18],[239,5],[271,5],[272,19],[273,20],[274,5],[283,21],[284,5],[286,22],[287,23],[285,24],[288,25],[289,26],[290,27],[291,28],[292,29],[293,30],[294,31],[295,32],[296,33],[297,34],[253,35],[246,36],[250,37],[248,38],[251,39],[249,40],[252,41],[247,5],[245,42],[244,43],[234,5],[298,44],[118,45],[119,45],[120,46],[78,47],[121,48],[122,49],[123,50],[73,5],[76,51],[74,5],[75,5],[124,52],[125,53],[126,54],[127,55],[128,56],[129,57],[130,57],[132,5],[131,58],[133,59],[134,60],[135,61],[117,62],[77,5],[136,63],[137,64],[138,65],[170,66],[139,67],[140,68],[141,69],[142,70],[143,71],[144,72],[145,73],[146,74],[147,75],[148,76],[149,76],[150,77],[151,5],[152,78],[154,79],[153,80],[155,81],[156,82],[157,83],[158,84],[159,85],[160,86],[161,87],[162,88],[163,89],[164,90],[165,91],[166,92],[167,93],[168,94],[169,95],[236,5],[237,5],[302,96],[299,5],[301,97],[327,98],[328,99],[303,100],[306,100],[325,98],[326,98],[316,98],[315,101],[313,98],[308,98],[321,98],[319,98],[323,98],[307,98],[320,98],[324,98],[309,98],[310,98],[322,98],[304,98],[311,98],[312,98],[314,98],[318,98],[329,102],[317,98],[305,98],[342,103],[341,5],[336,102],[338,104],[337,102],[330,102],[331,102],[333,102],[335,102],[339,104],[340,104],[332,104],[334,104],[235,105],[240,106],[343,5],[344,5],[345,107],[346,5],[347,108],[79,5],[214,109],[218,110],[231,5],[228,111],[220,112],[219,5],[217,113],[221,5],[215,114],[222,5],[232,115],[223,5],[227,116],[229,117],[216,118],[230,119],[224,5],[225,5],[226,120],[276,5],[254,5],[259,121],[258,122],[257,123],[256,124],[255,5],[300,5],[208,5],[171,125],[65,126],[66,5],[71,127],[70,126],[67,126],[69,126],[64,5],[68,126],[282,128],[206,129],[207,130],[205,131],[193,132],[198,133],[199,134],[202,135],[201,136],[200,137],[203,138],[210,139],[213,140],[212,141],[211,142],[204,143],[194,144],[209,145],[196,146],[192,147],[197,148],[195,132],[280,149],[281,150],[279,151],[61,5],[62,5],[12,5],[10,5],[11,5],[16,5],[15,5],[2,5],[17,5],[18,5],[19,5],[20,5],[21,5],[22,5],[23,5],[24,5],[3,5],[25,5],[26,5],[4,5],[27,5],[31,5],[28,5],[29,5],[30,5],[32,5],[33,5],[34,5],[5,5],[35,5],[36,5],[37,5],[38,5],[6,5],[42,5],[39,5],[40,5],[41,5],[43,5],[7,5],[44,5],[49,5],[50,5],[45,5],[46,5],[47,5],[48,5],[8,5],[54,5],[51,5],[52,5],[53,5],[55,5],[9,5],[56,5],[63,5],[57,5],[58,5],[60,5],[59,5],[1,5],[14,5],[13,5],[95,152],[105,153],[94,152],[115,154],[86,155],[85,156],[114,157],[108,158],[113,159],[88,160],[102,161],[87,162],[111,163],[83,164],[82,157],[112,165],[84,166],[89,167],[90,5],[93,167],[80,5],[116,168],[106,169],[97,170],[98,171],[100,172],[96,173],[99,174],[109,157],[91,175],[92,176],[101,177],[81,178],[104,169],[103,167],[107,5],[110,179]],"latestChangedDtsFile":"./dist/example.d.ts","version":"5.8.3"} \ No newline at end of file diff --git a/libs/config/validate-config.js b/libs/config/validate-config.js new file mode 100644 index 0000000..4a66e8f --- /dev/null +++ b/libs/config/validate-config.js @@ -0,0 +1,118 @@ +#!/usr/bin/env node + +/** + * Configuration Validation Script + * Tests that all configuration modules can be loaded and validated + */ + +// Set test environment variables +process.env.NODE_ENV = 'test'; +process.env.PORT = '3001'; + +// Database configs +process.env.DB_HOST = 'localhost'; +process.env.DB_PORT = '5432'; +process.env.DB_NAME = 'test_db'; +process.env.DB_USER = 'test_user'; +process.env.DB_PASSWORD = 'test_pass'; + +// QuestDB configs +process.env.QUESTDB_HOST = 'localhost'; +process.env.QUESTDB_HTTP_PORT = '9000'; +process.env.QUESTDB_PG_PORT = '8812'; + +// MongoDB configs +process.env.MONGODB_HOST = 'localhost'; +process.env.MONGODB_PORT = '27017'; +process.env.MONGODB_DATABASE = 'test_db'; + +// Dragonfly configs +process.env.DRAGONFLY_HOST = 'localhost'; +process.env.DRAGONFLY_PORT = '6379'; + +// Monitoring configs +process.env.PROMETHEUS_HOST = 'localhost'; +process.env.PROMETHEUS_PORT = '9090'; +process.env.GRAFANA_HOST = 'localhost'; +process.env.GRAFANA_PORT = '3000'; + +// Loki configs +process.env.LOKI_HOST = 'localhost'; +process.env.LOKI_PORT = '3100'; + +// Logging configs +process.env.LOG_LEVEL = 'info'; +process.env.LOG_FORMAT = 'json'; + +try { + console.log('🔍 Validating configuration modules...\n'); + + // Test each configuration module + const modules = [ + { name: 'Database', path: './dist/database.js' }, + { name: 'QuestDB', path: './dist/questdb.js' }, + { name: 'MongoDB', path: './dist/mongodb.js' }, + { name: 'Dragonfly', path: './dist/dragonfly.js' }, + { name: 'Monitoring', path: './dist/monitoring.js' }, + { name: 'Loki', path: './dist/loki.js' }, + { name: 'Logging', path: './dist/logging.js' }, + ]; + + const results = []; + + for (const module of modules) { + try { + const config = require(module.path); + const configKeys = Object.keys(config); + + if (configKeys.length === 0) { + throw new Error('No exported configuration found'); + } + + // Try to access the main config object + const mainConfig = config[configKeys[0]]; + if (!mainConfig || typeof mainConfig !== 'object') { + throw new Error('Invalid configuration object'); + } + + console.log(`✅ ${module.name}: ${configKeys.length} config(s) loaded`); + results.push({ name: module.name, status: 'success', configs: configKeys }); + + } catch (error) { + console.log(`❌ ${module.name}: ${error.message}`); + results.push({ name: module.name, status: 'error', error: error.message }); + } + } + + // Test main index exports + try { + const indexExports = require('./dist/index.js'); + const exportCount = Object.keys(indexExports).length; + console.log(`\n✅ Index exports: ${exportCount} modules exported`); + results.push({ name: 'Index', status: 'success', exports: exportCount }); + } catch (error) { + console.log(`\n❌ Index exports: ${error.message}`); + results.push({ name: 'Index', status: 'error', error: error.message }); + } + + // Summary + const successful = results.filter(r => r.status === 'success').length; + const total = results.length; + + console.log(`\n📊 Validation Summary:`); + console.log(` Total modules: ${total}`); + console.log(` Successful: ${successful}`); + console.log(` Failed: ${total - successful}`); + + if (successful === total) { + console.log('\n🎉 All configuration modules validated successfully!'); + process.exit(0); + } else { + console.log('\n⚠️ Some configuration modules failed validation.'); + process.exit(1); + } + +} catch (error) { + console.error('❌ Validation script failed:', error.message); + process.exit(1); +} diff --git a/package.json b/package.json index 070a8ce..0ccf654 100644 --- a/package.json +++ b/package.json @@ -39,5 +39,8 @@ "engines": { "node": ">=18.0.0", "bun": ">=1.1.0" + }, + "dependencies": { + "valibot": "^1.1.0" } }