running prettier for cleanup

This commit is contained in:
Boki 2025-06-11 10:13:25 -04:00
parent fe7733aeb5
commit d85cd58acd
151 changed files with 29158 additions and 27966 deletions

View file

@ -1,111 +1,118 @@
/**
* Admin interfaces configuration using Yup
* PgAdmin, Mongo Express, Redis Insight for database management
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, bool, strWithChoices } = envValidators;
/**
* PgAdmin configuration with validation and defaults
*/
export const pgAdminConfig = cleanEnv(process.env, {
// PgAdmin Server
PGADMIN_HOST: str('localhost', 'PgAdmin host'),
PGADMIN_PORT: port(8080, 'PgAdmin port'),
// Authentication
PGADMIN_DEFAULT_EMAIL: str('admin@tradingbot.local', 'PgAdmin default admin email'),
PGADMIN_DEFAULT_PASSWORD: str('admin123', 'PgAdmin default admin password'),
// Configuration
PGADMIN_SERVER_MODE: bool(false, 'Enable server mode (multi-user)'),
PGADMIN_DISABLE_POSTFIX: bool(true, 'Disable postfix for email'),
PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: bool(true, 'Enhanced cookie protection'),
// Security
PGADMIN_MASTER_PASSWORD_REQUIRED: bool(false, 'Require master password'),
PGADMIN_SESSION_TIMEOUT: str('60', 'Session timeout in minutes'),
});
/**
* Mongo Express configuration with validation and defaults
*/
export const mongoExpressConfig = cleanEnv(process.env, {
// Mongo Express Server
MONGO_EXPRESS_HOST: str('localhost', 'Mongo Express host'),
MONGO_EXPRESS_PORT: port(8081, 'Mongo Express port'),
// MongoDB Connection
MONGO_EXPRESS_MONGODB_SERVER: str('mongodb', 'MongoDB server name/host'),
MONGO_EXPRESS_MONGODB_PORT: port(27017, 'MongoDB port'),
MONGO_EXPRESS_MONGODB_ADMINUSERNAME: str('trading_admin', 'MongoDB admin username'),
MONGO_EXPRESS_MONGODB_ADMINPASSWORD: str('', 'MongoDB admin password'),
// Basic Authentication for Mongo Express
MONGO_EXPRESS_BASICAUTH_USERNAME: str('admin', 'Basic auth username for Mongo Express'),
MONGO_EXPRESS_BASICAUTH_PASSWORD: str('admin123', 'Basic auth password for Mongo Express'),
// Configuration
MONGO_EXPRESS_ENABLE_ADMIN: bool(true, 'Enable admin features'),
MONGO_EXPRESS_OPTIONS_EDITOR_THEME: str('rubyblue', 'Editor theme (rubyblue, 3024-night, etc.)'),
MONGO_EXPRESS_REQUEST_SIZE: str('100kb', 'Maximum request size'),
});
/**
* Redis Insight configuration with validation and defaults
*/
export const redisInsightConfig = cleanEnv(process.env, {
// Redis Insight Server
REDIS_INSIGHT_HOST: str('localhost', 'Redis Insight host'),
REDIS_INSIGHT_PORT: port(8001, 'Redis Insight port'),
// Redis Connection Settings
REDIS_INSIGHT_REDIS_HOSTS: str('local:dragonfly:6379', 'Redis hosts in format name:host:port,name:host:port'),
// Configuration
REDIS_INSIGHT_LOG_LEVEL: strWithChoices(['error', 'warn', 'info', 'verbose', 'debug'], 'info', 'Redis Insight log level'),
REDIS_INSIGHT_DISABLE_ANALYTICS: bool(true, 'Disable analytics collection'),
REDIS_INSIGHT_BUILD_TYPE: str('DOCKER', '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;
/**
* Admin interfaces configuration using Yup
* PgAdmin, Mongo Express, Redis Insight for database management
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, bool, strWithChoices } = envValidators;
/**
* PgAdmin configuration with validation and defaults
*/
export const pgAdminConfig = cleanEnv(process.env, {
// PgAdmin Server
PGADMIN_HOST: str('localhost', 'PgAdmin host'),
PGADMIN_PORT: port(8080, 'PgAdmin port'),
// Authentication
PGADMIN_DEFAULT_EMAIL: str('admin@tradingbot.local', 'PgAdmin default admin email'),
PGADMIN_DEFAULT_PASSWORD: str('admin123', 'PgAdmin default admin password'),
// Configuration
PGADMIN_SERVER_MODE: bool(false, 'Enable server mode (multi-user)'),
PGADMIN_DISABLE_POSTFIX: bool(true, 'Disable postfix for email'),
PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: bool(true, 'Enhanced cookie protection'),
// Security
PGADMIN_MASTER_PASSWORD_REQUIRED: bool(false, 'Require master password'),
PGADMIN_SESSION_TIMEOUT: str('60', 'Session timeout in minutes'),
});
/**
* Mongo Express configuration with validation and defaults
*/
export const mongoExpressConfig = cleanEnv(process.env, {
// Mongo Express Server
MONGO_EXPRESS_HOST: str('localhost', 'Mongo Express host'),
MONGO_EXPRESS_PORT: port(8081, 'Mongo Express port'),
// MongoDB Connection
MONGO_EXPRESS_MONGODB_SERVER: str('mongodb', 'MongoDB server name/host'),
MONGO_EXPRESS_MONGODB_PORT: port(27017, 'MongoDB port'),
MONGO_EXPRESS_MONGODB_ADMINUSERNAME: str('trading_admin', 'MongoDB admin username'),
MONGO_EXPRESS_MONGODB_ADMINPASSWORD: str('', 'MongoDB admin password'),
// Basic Authentication for Mongo Express
MONGO_EXPRESS_BASICAUTH_USERNAME: str('admin', 'Basic auth username for Mongo Express'),
MONGO_EXPRESS_BASICAUTH_PASSWORD: str('admin123', 'Basic auth password for Mongo Express'),
// Configuration
MONGO_EXPRESS_ENABLE_ADMIN: bool(true, 'Enable admin features'),
MONGO_EXPRESS_OPTIONS_EDITOR_THEME: str('rubyblue', 'Editor theme (rubyblue, 3024-night, etc.)'),
MONGO_EXPRESS_REQUEST_SIZE: str('100kb', 'Maximum request size'),
});
/**
* Redis Insight configuration with validation and defaults
*/
export const redisInsightConfig = cleanEnv(process.env, {
// Redis Insight Server
REDIS_INSIGHT_HOST: str('localhost', 'Redis Insight host'),
REDIS_INSIGHT_PORT: port(8001, 'Redis Insight port'),
// Redis Connection Settings
REDIS_INSIGHT_REDIS_HOSTS: str(
'local:dragonfly:6379',
'Redis hosts in format name:host:port,name:host:port'
),
// Configuration
REDIS_INSIGHT_LOG_LEVEL: strWithChoices(
['error', 'warn', 'info', 'verbose', 'debug'],
'info',
'Redis Insight log level'
),
REDIS_INSIGHT_DISABLE_ANALYTICS: bool(true, 'Disable analytics collection'),
REDIS_INSIGHT_BUILD_TYPE: str('DOCKER', '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;

View file

@ -1,68 +1,63 @@
/**
* Core configuration module for the Stock Bot platform using Yup
*/
import { config as dotenvConfig } from 'dotenv';
import path from 'node:path';
/**
* Represents an error related to configuration validation
*/
export class ConfigurationError extends Error {
constructor(message: string) {
super(message);
this.name = 'ConfigurationError';
}
}
/**
* Environment types
*/
export enum Environment {
Development = 'development',
Testing = 'testing',
Staging = 'staging',
Production = 'production'
}
/**
* Loads environment variables from .env files based on the current environment
*/
export function loadEnvVariables(envOverride?: string): void {
const env = envOverride || process.env.NODE_ENV || 'development';
console.log(`Current environment: ${env}`);
// Order of loading:
// 1. .env (base environment variables)
// 2. .env.{environment} (environment-specific variables)
// 3. .env.local (local overrides, not to be committed)
const envFiles = [
'.env',
`.env.${env}`,
'.env.local'
];
for (const file of envFiles) {
dotenvConfig({ path: path.resolve(process.cwd(), file) });
}
}
/**
* Gets the current environment from process.env.NODE_ENV
*/
export function getEnvironment(): Environment {
const env = process.env.NODE_ENV?.toLowerCase() || 'development';
switch (env) {
case 'development':
return Environment.Development;
case 'testing':
case 'test': // Handle both 'test' and 'testing' for compatibility
return Environment.Testing;
case 'staging':
return Environment.Staging;
case 'production':
return Environment.Production;
default:
return Environment.Development;
}
}
/**
* Core configuration module for the Stock Bot platform using Yup
*/
import path from 'node:path';
import { config as dotenvConfig } from 'dotenv';
/**
* Represents an error related to configuration validation
*/
export class ConfigurationError extends Error {
constructor(message: string) {
super(message);
this.name = 'ConfigurationError';
}
}
/**
* Environment types
*/
export enum Environment {
Development = 'development',
Testing = 'testing',
Staging = 'staging',
Production = 'production',
}
/**
* Loads environment variables from .env files based on the current environment
*/
export function loadEnvVariables(envOverride?: string): void {
const env = envOverride || process.env.NODE_ENV || 'development';
console.log(`Current environment: ${env}`);
// Order of loading:
// 1. .env (base environment variables)
// 2. .env.{environment} (environment-specific variables)
// 3. .env.local (local overrides, not to be committed)
const envFiles = ['.env', `.env.${env}`, '.env.local'];
for (const file of envFiles) {
dotenvConfig({ path: path.resolve(process.cwd(), file) });
}
}
/**
* Gets the current environment from process.env.NODE_ENV
*/
export function getEnvironment(): Environment {
const env = process.env.NODE_ENV?.toLowerCase() || 'development';
switch (env) {
case 'development':
return Environment.Development;
case 'testing':
case 'test': // Handle both 'test' and 'testing' for compatibility
return Environment.Testing;
case 'staging':
return Environment.Staging;
case 'production':
return Environment.Production;
default:
return Environment.Development;
}
}

View file

@ -1,184 +1,185 @@
/**
* Data provider configurations using Yup
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, num, bool, strWithChoices } = envValidators;
export interface ProviderConfig {
name: string;
type: 'rest' | 'websocket';
enabled: boolean;
baseUrl?: string;
apiKey?: string;
apiSecret?: string;
rateLimits?: {
maxRequestsPerMinute?: number;
maxRequestsPerSecond?: number;
maxRequestsPerHour?: number;
};
}
/**
* Data providers configuration with validation and defaults
*/
export const dataProvidersConfig = cleanEnv(process.env, {
// Default Provider
DEFAULT_DATA_PROVIDER: strWithChoices(['alpaca', 'polygon', 'yahoo', 'iex'], 'alpaca', 'Default data provider'),
// Alpaca Configuration
ALPACA_API_KEY: str('', 'Alpaca API key'),
ALPACA_API_SECRET: str('', 'Alpaca API secret'),
ALPACA_BASE_URL: str('https://data.alpaca.markets/v1beta1', 'Alpaca base URL'),
ALPACA_RATE_LIMIT: num(200, 'Alpaca rate limit per minute'),
ALPACA_ENABLED: bool(true, 'Enable Alpaca provider'),
// Polygon Configuration
POLYGON_API_KEY: str('', 'Polygon API key'),
POLYGON_BASE_URL: str('https://api.polygon.io', 'Polygon base URL'),
POLYGON_RATE_LIMIT: num(5, 'Polygon rate limit per minute'),
POLYGON_ENABLED: bool(false, 'Enable Polygon provider'),
// Yahoo Finance Configuration
YAHOO_BASE_URL: str('https://query1.finance.yahoo.com', 'Yahoo Finance base URL'),
YAHOO_RATE_LIMIT: num(2000, 'Yahoo Finance rate limit per hour'),
YAHOO_ENABLED: bool(true, 'Enable Yahoo Finance provider'),
// IEX Cloud Configuration
IEX_API_KEY: str('', 'IEX Cloud API key'),
IEX_BASE_URL: str('https://cloud.iexapis.com/stable', 'IEX Cloud base URL'),
IEX_RATE_LIMIT: num(100, 'IEX Cloud rate limit per second'),
IEX_ENABLED: bool(false, 'Enable IEX Cloud provider'),
// Connection Settings
DATA_PROVIDER_TIMEOUT: num(30000, 'Request timeout in milliseconds'),
DATA_PROVIDER_RETRIES: num(3, 'Number of retry attempts'),
DATA_PROVIDER_RETRY_DELAY: num(1000, 'Retry delay in milliseconds'),
// Cache Settings
DATA_CACHE_ENABLED: bool(true, 'Enable data caching'),
DATA_CACHE_TTL: num(300000, 'Cache TTL in milliseconds'),
DATA_CACHE_MAX_SIZE: num(1000, 'Maximum cache entries'),
});
/**
* Helper function to get provider-specific configuration
*/
export function getProviderConfig(providerName: string) {
// make a interface for the provider config
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}`);
}
}
/**
* Get all enabled providers
*/
export function getEnabledProviders() {
const providers = ['alpaca', 'polygon', 'yahoo', 'iex'];
return providers
.map(provider => getProviderConfig(provider))
.filter(config => config.enabled);
}
/**
* Get the default provider configuration
*/
export function getDefaultProvider() {
return getProviderConfig(dataProvidersConfig.DEFAULT_DATA_PROVIDER);
}
// Export typed configuration object
export type DataProvidersConfig = typeof dataProvidersConfig;
export class DataProviders {
static getProviderConfig(providerName: string): ProviderConfig {
return getProviderConfig(providerName);
}
static getEnabledProviders(): ProviderConfig[] {
return getEnabledProviders();
}
static getDefaultProvider(): ProviderConfig {
return getDefaultProvider();
}
}
// 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;
/**
* Data provider configurations using Yup
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, num, bool, strWithChoices } = envValidators;
export interface ProviderConfig {
name: string;
type: 'rest' | 'websocket';
enabled: boolean;
baseUrl?: string;
apiKey?: string;
apiSecret?: string;
rateLimits?: {
maxRequestsPerMinute?: number;
maxRequestsPerSecond?: number;
maxRequestsPerHour?: number;
};
}
/**
* Data providers configuration with validation and defaults
*/
export const dataProvidersConfig = cleanEnv(process.env, {
// Default Provider
DEFAULT_DATA_PROVIDER: strWithChoices(
['alpaca', 'polygon', 'yahoo', 'iex'],
'alpaca',
'Default data provider'
),
// Alpaca Configuration
ALPACA_API_KEY: str('', 'Alpaca API key'),
ALPACA_API_SECRET: str('', 'Alpaca API secret'),
ALPACA_BASE_URL: str('https://data.alpaca.markets/v1beta1', 'Alpaca base URL'),
ALPACA_RATE_LIMIT: num(200, 'Alpaca rate limit per minute'),
ALPACA_ENABLED: bool(true, 'Enable Alpaca provider'),
// Polygon Configuration
POLYGON_API_KEY: str('', 'Polygon API key'),
POLYGON_BASE_URL: str('https://api.polygon.io', 'Polygon base URL'),
POLYGON_RATE_LIMIT: num(5, 'Polygon rate limit per minute'),
POLYGON_ENABLED: bool(false, 'Enable Polygon provider'),
// Yahoo Finance Configuration
YAHOO_BASE_URL: str('https://query1.finance.yahoo.com', 'Yahoo Finance base URL'),
YAHOO_RATE_LIMIT: num(2000, 'Yahoo Finance rate limit per hour'),
YAHOO_ENABLED: bool(true, 'Enable Yahoo Finance provider'),
// IEX Cloud Configuration
IEX_API_KEY: str('', 'IEX Cloud API key'),
IEX_BASE_URL: str('https://cloud.iexapis.com/stable', 'IEX Cloud base URL'),
IEX_RATE_LIMIT: num(100, 'IEX Cloud rate limit per second'),
IEX_ENABLED: bool(false, 'Enable IEX Cloud provider'),
// Connection Settings
DATA_PROVIDER_TIMEOUT: num(30000, 'Request timeout in milliseconds'),
DATA_PROVIDER_RETRIES: num(3, 'Number of retry attempts'),
DATA_PROVIDER_RETRY_DELAY: num(1000, 'Retry delay in milliseconds'),
// Cache Settings
DATA_CACHE_ENABLED: bool(true, 'Enable data caching'),
DATA_CACHE_TTL: num(300000, 'Cache TTL in milliseconds'),
DATA_CACHE_MAX_SIZE: num(1000, 'Maximum cache entries'),
});
/**
* Helper function to get provider-specific configuration
*/
export function getProviderConfig(providerName: string) {
// make a interface for the provider config
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}`);
}
}
/**
* Get all enabled providers
*/
export function getEnabledProviders() {
const providers = ['alpaca', 'polygon', 'yahoo', 'iex'];
return providers.map(provider => getProviderConfig(provider)).filter(config => config.enabled);
}
/**
* Get the default provider configuration
*/
export function getDefaultProvider() {
return getProviderConfig(dataProvidersConfig.DEFAULT_DATA_PROVIDER);
}
// Export typed configuration object
export type DataProvidersConfig = typeof dataProvidersConfig;
export class DataProviders {
static getProviderConfig(providerName: string): ProviderConfig {
return getProviderConfig(providerName);
}
static getEnabledProviders(): ProviderConfig[] {
return getEnabledProviders();
}
static getDefaultProvider(): ProviderConfig {
return getDefaultProvider();
}
}
// 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;

View file

@ -1,56 +1,56 @@
/**
* Database configuration using Yup
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, num, bool } = envValidators;
/**
* Database configuration with validation and defaults
*/
export const databaseConfig = cleanEnv(process.env, {
// PostgreSQL Configuration
DB_HOST: str('localhost', 'Database host'),
DB_PORT: port(5432, 'Database port'),
DB_NAME: str('stockbot', 'Database name'),
DB_USER: str('stockbot', 'Database user'),
DB_PASSWORD: str('', 'Database password'),
// Connection Pool Settings
DB_POOL_MIN: num(2, 'Minimum pool connections'),
DB_POOL_MAX: num(10, 'Maximum pool connections'),
DB_POOL_IDLE_TIMEOUT: num(30000, 'Pool idle timeout in ms'),
// SSL Configuration
DB_SSL: bool(false, 'Enable SSL for database connection'),
DB_SSL_REJECT_UNAUTHORIZED: bool(true, 'Reject unauthorized SSL certificates'),
// Additional Settings
DB_QUERY_TIMEOUT: num(30000, 'Query timeout in ms'),
DB_CONNECTION_TIMEOUT: num(5000, 'Connection timeout in ms'),
DB_STATEMENT_TIMEOUT: num(30000, 'Statement timeout in ms'),
DB_LOCK_TIMEOUT: num(10000, 'Lock timeout in ms'),
DB_IDLE_IN_TRANSACTION_SESSION_TIMEOUT: num(60000, 'Idle in transaction timeout in ms'),
});
// Export typed configuration object
export type DatabaseConfig = typeof databaseConfig;
// 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;
/**
* Database configuration using Yup
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, num, bool } = envValidators;
/**
* Database configuration with validation and defaults
*/
export const databaseConfig = cleanEnv(process.env, {
// PostgreSQL Configuration
DB_HOST: str('localhost', 'Database host'),
DB_PORT: port(5432, 'Database port'),
DB_NAME: str('stockbot', 'Database name'),
DB_USER: str('stockbot', 'Database user'),
DB_PASSWORD: str('', 'Database password'),
// Connection Pool Settings
DB_POOL_MIN: num(2, 'Minimum pool connections'),
DB_POOL_MAX: num(10, 'Maximum pool connections'),
DB_POOL_IDLE_TIMEOUT: num(30000, 'Pool idle timeout in ms'),
// SSL Configuration
DB_SSL: bool(false, 'Enable SSL for database connection'),
DB_SSL_REJECT_UNAUTHORIZED: bool(true, 'Reject unauthorized SSL certificates'),
// Additional Settings
DB_QUERY_TIMEOUT: num(30000, 'Query timeout in ms'),
DB_CONNECTION_TIMEOUT: num(5000, 'Connection timeout in ms'),
DB_STATEMENT_TIMEOUT: num(30000, 'Statement timeout in ms'),
DB_LOCK_TIMEOUT: num(10000, 'Lock timeout in ms'),
DB_IDLE_IN_TRANSACTION_SESSION_TIMEOUT: num(60000, 'Idle in transaction timeout in ms'),
});
// Export typed configuration object
export type DatabaseConfig = typeof databaseConfig;
// 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;

View file

@ -1,81 +1,81 @@
/**
* Dragonfly (Redis replacement) configuration using Yup
* High-performance caching and event streaming
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, num, bool } = envValidators;
/**
* Dragonfly configuration with validation and defaults
*/
export const dragonflyConfig = cleanEnv(process.env, {
// Dragonfly Connection
DRAGONFLY_HOST: str('localhost', 'Dragonfly host'),
DRAGONFLY_PORT: port(6379, 'Dragonfly port'),
DRAGONFLY_PASSWORD: str('', 'Dragonfly password (if auth enabled)'),
DRAGONFLY_USERNAME: str('', 'Dragonfly username (if ACL enabled)'),
// Database Selection
DRAGONFLY_DATABASE: num(0, 'Dragonfly database number (0-15)'),
// Connection Pool Settings
DRAGONFLY_MAX_RETRIES: num(3, 'Maximum retry attempts'),
DRAGONFLY_RETRY_DELAY: num(50, 'Retry delay in ms'),
DRAGONFLY_CONNECT_TIMEOUT: num(10000, 'Connection timeout in ms'),
DRAGONFLY_COMMAND_TIMEOUT: num(5000, 'Command timeout in ms'),
// Pool Configuration
DRAGONFLY_POOL_SIZE: num(10, 'Connection pool size'),
DRAGONFLY_POOL_MIN: num(1, 'Minimum pool connections'),
DRAGONFLY_POOL_MAX: num(20, 'Maximum pool connections'),
// TLS Settings
DRAGONFLY_TLS: bool(false, 'Enable TLS for Dragonfly connection'),
DRAGONFLY_TLS_CERT_FILE: str('', 'Path to TLS certificate file'),
DRAGONFLY_TLS_KEY_FILE: str('', 'Path to TLS key file'),
DRAGONFLY_TLS_CA_FILE: str('', 'Path to TLS CA certificate file'),
DRAGONFLY_TLS_SKIP_VERIFY: bool(false, 'Skip TLS certificate verification'),
// Performance Settings
DRAGONFLY_ENABLE_KEEPALIVE: bool(true, 'Enable TCP keepalive'),
DRAGONFLY_KEEPALIVE_INTERVAL: num(60, 'Keepalive interval in seconds'),
// Clustering (if using cluster mode)
DRAGONFLY_CLUSTER_MODE: bool(false, 'Enable cluster mode'),
DRAGONFLY_CLUSTER_NODES: str('', 'Comma-separated list of cluster nodes (host:port)'),
// Memory and Cache Settings
DRAGONFLY_MAX_MEMORY: str('2gb', 'Maximum memory usage'),
DRAGONFLY_CACHE_MODE: bool(true, '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;
/**
* Dragonfly (Redis replacement) configuration using Yup
* High-performance caching and event streaming
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, num, bool } = envValidators;
/**
* Dragonfly configuration with validation and defaults
*/
export const dragonflyConfig = cleanEnv(process.env, {
// Dragonfly Connection
DRAGONFLY_HOST: str('localhost', 'Dragonfly host'),
DRAGONFLY_PORT: port(6379, 'Dragonfly port'),
DRAGONFLY_PASSWORD: str('', 'Dragonfly password (if auth enabled)'),
DRAGONFLY_USERNAME: str('', 'Dragonfly username (if ACL enabled)'),
// Database Selection
DRAGONFLY_DATABASE: num(0, 'Dragonfly database number (0-15)'),
// Connection Pool Settings
DRAGONFLY_MAX_RETRIES: num(3, 'Maximum retry attempts'),
DRAGONFLY_RETRY_DELAY: num(50, 'Retry delay in ms'),
DRAGONFLY_CONNECT_TIMEOUT: num(10000, 'Connection timeout in ms'),
DRAGONFLY_COMMAND_TIMEOUT: num(5000, 'Command timeout in ms'),
// Pool Configuration
DRAGONFLY_POOL_SIZE: num(10, 'Connection pool size'),
DRAGONFLY_POOL_MIN: num(1, 'Minimum pool connections'),
DRAGONFLY_POOL_MAX: num(20, 'Maximum pool connections'),
// TLS Settings
DRAGONFLY_TLS: bool(false, 'Enable TLS for Dragonfly connection'),
DRAGONFLY_TLS_CERT_FILE: str('', 'Path to TLS certificate file'),
DRAGONFLY_TLS_KEY_FILE: str('', 'Path to TLS key file'),
DRAGONFLY_TLS_CA_FILE: str('', 'Path to TLS CA certificate file'),
DRAGONFLY_TLS_SKIP_VERIFY: bool(false, 'Skip TLS certificate verification'),
// Performance Settings
DRAGONFLY_ENABLE_KEEPALIVE: bool(true, 'Enable TCP keepalive'),
DRAGONFLY_KEEPALIVE_INTERVAL: num(60, 'Keepalive interval in seconds'),
// Clustering (if using cluster mode)
DRAGONFLY_CLUSTER_MODE: bool(false, 'Enable cluster mode'),
DRAGONFLY_CLUSTER_NODES: str('', 'Comma-separated list of cluster nodes (host:port)'),
// Memory and Cache Settings
DRAGONFLY_MAX_MEMORY: str('2gb', 'Maximum memory usage'),
DRAGONFLY_CACHE_MODE: bool(true, '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;

View file

@ -1,162 +1,165 @@
/**
* Environment validation utilities using Yup
*/
import * as yup from 'yup';
import { config } from 'dotenv';
import { join } from 'path';
import { existsSync } from 'fs';
// Function to find and load environment variables
function loadEnvFiles() {
const cwd = process.cwd();
const possiblePaths = [
// Current working directory
join(cwd, '.env'),
join(cwd, '.env.local'),
// Root of the workspace (common pattern)
join(cwd, '../../.env'),
join(cwd, '../../../.env'),
// Config library directory
join(__dirname, '../.env'),
join(__dirname, '../../.env'),
join(__dirname, '../../../.env'),
];
// Try to load each possible .env file
for (const envPath of possiblePaths) {
if (existsSync(envPath)) {
console.log(`📄 Loading environment from: ${envPath}`);
config({ path: envPath });
break; // Use the first .env file found
}
}
// Also try to load environment-specific files
const environment = process.env.NODE_ENV || 'development';
const envSpecificPaths = [
join(cwd, `.env.${environment}`),
join(cwd, `.env.${environment}.local`),
];
for (const envPath of envSpecificPaths) {
if (existsSync(envPath)) {
console.log(`📄 Loading ${environment} environment from: ${envPath}`);
config({ path: envPath, override: false }); // Don't override existing vars
}
}
}
// Load environment variables
loadEnvFiles();
/**
* Creates a Yup schema for environment variable validation
*/
export function createEnvSchema(shape: Record<string, any>) {
return yup.object(shape);
}
/**
* Validates environment variables against a Yup schema
*/
export function validateEnv(
schema: yup.ObjectSchema<any>,
env = process.env
): any {
try {
const result = schema.validateSync(env, { abortEarly: false });
return result;
} catch (error) {
if (error instanceof yup.ValidationError) {
console.error('❌ Invalid environment variables:');
error.inner.forEach((err) => {
console.error(` ${err.path}: ${err.message}`);
});
}
throw new Error('Environment validation failed');
}
}
/**
* Manually load environment variables from a specific path
*/
export function loadEnv(path?: string) {
if (path) {
console.log(`📄 Manually loading environment from: ${path}`);
config({ path });
} else {
loadEnvFiles();
}
}
/**
* Helper functions for common validation patterns
*/
export const envValidators = {
// String with default
str: (defaultValue?: string, description?: string) =>
yup.string().default(defaultValue || ''),
// String with choices (enum)
strWithChoices: (choices: string[], defaultValue?: string, description?: string) =>
yup.string().oneOf(choices).default(defaultValue || choices[0]),
// Required string
requiredStr: (description?: string) =>
yup.string().required('Required'),
// Port number
port: (defaultValue?: number, description?: string) =>
yup.number()
.integer()
.min(1)
.max(65535)
.transform((val, originalVal) => {
if (typeof originalVal === 'string') {
return parseInt(originalVal, 10);
}
return val;
})
.default(defaultValue || 3000),
// Number with default
num: (defaultValue?: number, description?: string) =>
yup.number()
.transform((val, originalVal) => {
if (typeof originalVal === 'string') {
return parseFloat(originalVal);
}
return val;
})
.default(defaultValue || 0),
// Boolean with default
bool: (defaultValue?: boolean, description?: string) =>
yup.boolean()
.transform((val, originalVal) => {
if (typeof originalVal === 'string') {
return originalVal === 'true' || originalVal === '1';
}
return val;
})
.default(defaultValue || false),
// URL validation
url: (defaultValue?: string, description?: string) =>
yup.string().url().default(defaultValue || 'http://localhost'),
// Email validation
email: (description?: string) =>
yup.string().email(),
};
/**
* Legacy compatibility - creates a cleanEnv-like function
*/
export function cleanEnv(
env: Record<string, string | undefined>,
validators: Record<string, any>
): any {
const schema = createEnvSchema(validators);
return validateEnv(schema, env);
}
/**
* Environment validation utilities using Yup
*/
import { existsSync } from 'fs';
import { join } from 'path';
import { config } from 'dotenv';
import * as yup from 'yup';
// Function to find and load environment variables
function loadEnvFiles() {
const cwd = process.cwd();
const possiblePaths = [
// Current working directory
join(cwd, '.env'),
join(cwd, '.env.local'),
// Root of the workspace (common pattern)
join(cwd, '../../.env'),
join(cwd, '../../../.env'),
// Config library directory
join(__dirname, '../.env'),
join(__dirname, '../../.env'),
join(__dirname, '../../../.env'),
];
// Try to load each possible .env file
for (const envPath of possiblePaths) {
if (existsSync(envPath)) {
console.log(`📄 Loading environment from: ${envPath}`);
config({ path: envPath });
break; // Use the first .env file found
}
}
// Also try to load environment-specific files
const environment = process.env.NODE_ENV || 'development';
const envSpecificPaths = [
join(cwd, `.env.${environment}`),
join(cwd, `.env.${environment}.local`),
];
for (const envPath of envSpecificPaths) {
if (existsSync(envPath)) {
console.log(`📄 Loading ${environment} environment from: ${envPath}`);
config({ path: envPath, override: false }); // Don't override existing vars
}
}
}
// Load environment variables
loadEnvFiles();
/**
* Creates a Yup schema for environment variable validation
*/
export function createEnvSchema(shape: Record<string, any>) {
return yup.object(shape);
}
/**
* Validates environment variables against a Yup schema
*/
export function validateEnv(schema: yup.ObjectSchema<any>, env = process.env): any {
try {
const result = schema.validateSync(env, { abortEarly: false });
return result;
} catch (error) {
if (error instanceof yup.ValidationError) {
console.error('❌ Invalid environment variables:');
error.inner.forEach(err => {
console.error(` ${err.path}: ${err.message}`);
});
}
throw new Error('Environment validation failed');
}
}
/**
* Manually load environment variables from a specific path
*/
export function loadEnv(path?: string) {
if (path) {
console.log(`📄 Manually loading environment from: ${path}`);
config({ path });
} else {
loadEnvFiles();
}
}
/**
* Helper functions for common validation patterns
*/
export const envValidators = {
// String with default
str: (defaultValue?: string, description?: string) => yup.string().default(defaultValue || ''),
// String with choices (enum)
strWithChoices: (choices: string[], defaultValue?: string, description?: string) =>
yup
.string()
.oneOf(choices)
.default(defaultValue || choices[0]),
// Required string
requiredStr: (description?: string) => yup.string().required('Required'),
// Port number
port: (defaultValue?: number, description?: string) =>
yup
.number()
.integer()
.min(1)
.max(65535)
.transform((val, originalVal) => {
if (typeof originalVal === 'string') {
return parseInt(originalVal, 10);
}
return val;
})
.default(defaultValue || 3000),
// Number with default
num: (defaultValue?: number, description?: string) =>
yup
.number()
.transform((val, originalVal) => {
if (typeof originalVal === 'string') {
return parseFloat(originalVal);
}
return val;
})
.default(defaultValue || 0),
// Boolean with default
bool: (defaultValue?: boolean, description?: string) =>
yup
.boolean()
.transform((val, originalVal) => {
if (typeof originalVal === 'string') {
return originalVal === 'true' || originalVal === '1';
}
return val;
})
.default(defaultValue || false),
// URL validation
url: (defaultValue?: string, description?: string) =>
yup
.string()
.url()
.default(defaultValue || 'http://localhost'),
// Email validation
email: (description?: string) => yup.string().email(),
};
/**
* Legacy compatibility - creates a cleanEnv-like function
*/
export function cleanEnv(
env: Record<string, string | undefined>,
validators: Record<string, any>
): any {
const schema = createEnvSchema(validators);
return validateEnv(schema, env);
}

View file

@ -1,20 +1,20 @@
/**
* @stock-bot/config
*
* Configuration management library for Stock Bot platform using Yup
*/
// Re-export everything from all modules
export * from './env-utils';
export * from './core';
export * from './admin-interfaces';
export * from './database';
export * from './dragonfly';
export * from './postgres';
export * from './questdb';
export * from './mongodb';
export * from './logging';
export * from './loki';
export * from './monitoring';
export * from './data-providers';
export * from './risk';
/**
* @stock-bot/config
*
* Configuration management library for Stock Bot platform using Yup
*/
// Re-export everything from all modules
export * from './env-utils';
export * from './core';
export * from './admin-interfaces';
export * from './database';
export * from './dragonfly';
export * from './postgres';
export * from './questdb';
export * from './mongodb';
export * from './logging';
export * from './loki';
export * from './monitoring';
export * from './data-providers';
export * from './risk';

View file

@ -1,74 +1,74 @@
/**
* Logging configuration using Yup
* Application logging settings without Loki (Loki config is in monitoring.ts)
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, bool, num, strWithChoices } = envValidators;
/**
* Logging configuration with validation and defaults
*/
export const loggingConfig = cleanEnv(process.env, {
// Basic Logging Settings
LOG_LEVEL: strWithChoices(['debug', 'info', 'warn', 'error'], 'info', 'Logging level'),
LOG_FORMAT: strWithChoices(['json', 'simple', 'combined'], 'json', 'Log output format'),
LOG_CONSOLE: bool(true, 'Enable console logging'),
LOG_FILE: bool(false, 'Enable file logging'),
// File Logging Settings
LOG_FILE_PATH: str('logs', 'Log file directory path'),
LOG_FILE_MAX_SIZE: str('20m', 'Maximum log file size'),
LOG_FILE_MAX_FILES: num(14, 'Maximum number of log files to keep'),
LOG_FILE_DATE_PATTERN: str('YYYY-MM-DD', 'Log file date pattern'),
// Error Logging
LOG_ERROR_FILE: bool(true, 'Enable separate error log file'),
LOG_ERROR_STACK: bool(true, 'Include stack traces in error logs'),
// Performance Logging
LOG_PERFORMANCE: bool(false, 'Enable performance logging'),
LOG_SQL_QUERIES: bool(false, 'Log SQL queries'),
LOG_HTTP_REQUESTS: bool(true, 'Log HTTP requests'),
// Structured Logging
LOG_STRUCTURED: bool(true, 'Use structured logging format'),
LOG_TIMESTAMP: bool(true, 'Include timestamps in logs'),
LOG_CALLER_INFO: bool(false, 'Include caller information in logs'),
// Log Filtering
LOG_SILENT_MODULES: str('', 'Comma-separated list of modules to silence'),
LOG_VERBOSE_MODULES: str('', 'Comma-separated list of modules for verbose logging'),
// Application Context
LOG_SERVICE_NAME: str('stock-bot', 'Service name for log context'),
LOG_SERVICE_VERSION: str('1.0.0', 'Service version for log context'),
LOG_ENVIRONMENT: str('development', 'Environment for log context'),
});
// Export typed configuration object
export type LoggingConfig = typeof loggingConfig;
// 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;
/**
* Logging configuration using Yup
* Application logging settings without Loki (Loki config is in monitoring.ts)
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, bool, num, strWithChoices } = envValidators;
/**
* Logging configuration with validation and defaults
*/
export const loggingConfig = cleanEnv(process.env, {
// Basic Logging Settings
LOG_LEVEL: strWithChoices(['debug', 'info', 'warn', 'error'], 'info', 'Logging level'),
LOG_FORMAT: strWithChoices(['json', 'simple', 'combined'], 'json', 'Log output format'),
LOG_CONSOLE: bool(true, 'Enable console logging'),
LOG_FILE: bool(false, 'Enable file logging'),
// File Logging Settings
LOG_FILE_PATH: str('logs', 'Log file directory path'),
LOG_FILE_MAX_SIZE: str('20m', 'Maximum log file size'),
LOG_FILE_MAX_FILES: num(14, 'Maximum number of log files to keep'),
LOG_FILE_DATE_PATTERN: str('YYYY-MM-DD', 'Log file date pattern'),
// Error Logging
LOG_ERROR_FILE: bool(true, 'Enable separate error log file'),
LOG_ERROR_STACK: bool(true, 'Include stack traces in error logs'),
// Performance Logging
LOG_PERFORMANCE: bool(false, 'Enable performance logging'),
LOG_SQL_QUERIES: bool(false, 'Log SQL queries'),
LOG_HTTP_REQUESTS: bool(true, 'Log HTTP requests'),
// Structured Logging
LOG_STRUCTURED: bool(true, 'Use structured logging format'),
LOG_TIMESTAMP: bool(true, 'Include timestamps in logs'),
LOG_CALLER_INFO: bool(false, 'Include caller information in logs'),
// Log Filtering
LOG_SILENT_MODULES: str('', 'Comma-separated list of modules to silence'),
LOG_VERBOSE_MODULES: str('', 'Comma-separated list of modules for verbose logging'),
// Application Context
LOG_SERVICE_NAME: str('stock-bot', 'Service name for log context'),
LOG_SERVICE_VERSION: str('1.0.0', 'Service version for log context'),
LOG_ENVIRONMENT: str('development', 'Environment for log context'),
});
// Export typed configuration object
export type LoggingConfig = typeof loggingConfig;
// 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;

View file

@ -1,63 +1,63 @@
/**
* Loki log aggregation configuration using Yup
* Centralized logging configuration for the Stock Bot platform
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, bool, num } = envValidators;
/**
* Loki configuration with validation and defaults
*/
export const lokiConfig = cleanEnv(process.env, {
// Loki Server
LOKI_HOST: str('localhost', 'Loki host'),
LOKI_PORT: port(3100, 'Loki port'),
LOKI_URL: str('', 'Complete Loki URL (overrides host/port)'),
// Authentication
LOKI_USERNAME: str('', 'Loki username (if auth enabled)'),
LOKI_PASSWORD: str('', 'Loki password (if auth enabled)'),
LOKI_TENANT_ID: str('', 'Loki tenant ID (for multi-tenancy)'),
// Push Configuration
LOKI_PUSH_TIMEOUT: num(10000, 'Push timeout in ms'),
LOKI_BATCH_SIZE: num(1024, 'Batch size for log entries'),
LOKI_BATCH_WAIT: num(5, 'Batch wait time in ms'),
// Retention Settings
LOKI_RETENTION_PERIOD: str('30d', 'Log retention period'),
LOKI_MAX_CHUNK_AGE: str('1h', 'Maximum chunk age'),
// TLS Settings
LOKI_TLS_ENABLED: bool(false, 'Enable TLS for Loki'),
LOKI_TLS_INSECURE: bool(false, 'Skip TLS verification'),
// Log Labels
LOKI_DEFAULT_LABELS: str('', 'Default labels for all log entries (JSON format)'),
LOKI_SERVICE_LABEL: str('stock-bot', 'Service label for log entries'),
LOKI_ENVIRONMENT_LABEL: str('development', '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;
/**
* Loki log aggregation configuration using Yup
* Centralized logging configuration for the Stock Bot platform
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, bool, num } = envValidators;
/**
* Loki configuration with validation and defaults
*/
export const lokiConfig = cleanEnv(process.env, {
// Loki Server
LOKI_HOST: str('localhost', 'Loki host'),
LOKI_PORT: port(3100, 'Loki port'),
LOKI_URL: str('', 'Complete Loki URL (overrides host/port)'),
// Authentication
LOKI_USERNAME: str('', 'Loki username (if auth enabled)'),
LOKI_PASSWORD: str('', 'Loki password (if auth enabled)'),
LOKI_TENANT_ID: str('', 'Loki tenant ID (for multi-tenancy)'),
// Push Configuration
LOKI_PUSH_TIMEOUT: num(10000, 'Push timeout in ms'),
LOKI_BATCH_SIZE: num(1024, 'Batch size for log entries'),
LOKI_BATCH_WAIT: num(5, 'Batch wait time in ms'),
// Retention Settings
LOKI_RETENTION_PERIOD: str('30d', 'Log retention period'),
LOKI_MAX_CHUNK_AGE: str('1h', 'Maximum chunk age'),
// TLS Settings
LOKI_TLS_ENABLED: bool(false, 'Enable TLS for Loki'),
LOKI_TLS_INSECURE: bool(false, 'Skip TLS verification'),
// Log Labels
LOKI_DEFAULT_LABELS: str('', 'Default labels for all log entries (JSON format)'),
LOKI_SERVICE_LABEL: str('stock-bot', 'Service label for log entries'),
LOKI_ENVIRONMENT_LABEL: str('development', '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;

View file

@ -1,73 +1,77 @@
/**
* MongoDB configuration using Yup
* Document storage for sentiment data, raw documents, and unstructured data
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, bool, num, strWithChoices } = envValidators;
/**
* MongoDB configuration with validation and defaults
*/
export const mongodbConfig = cleanEnv(process.env, {
// MongoDB Connection
MONGODB_HOST: str('localhost', 'MongoDB host'),
MONGODB_PORT: port(27017, 'MongoDB port'),
MONGODB_DATABASE: str('trading_documents', 'MongoDB database name'),
// Authentication
MONGODB_USERNAME: str('trading_admin', 'MongoDB username'),
MONGODB_PASSWORD: str('', 'MongoDB password'),
MONGODB_AUTH_SOURCE: str('admin', 'MongoDB authentication database'),
// Connection URI (alternative to individual settings)
MONGODB_URI: str('', 'Complete MongoDB connection URI (overrides individual settings)'),
// Connection Pool Settings
MONGODB_MAX_POOL_SIZE: num(10, 'Maximum connection pool size'),
MONGODB_MIN_POOL_SIZE: num(0, 'Minimum connection pool size'),
MONGODB_MAX_IDLE_TIME: num(30000, 'Maximum idle time for connections in ms'),
// Timeouts
MONGODB_CONNECT_TIMEOUT: num(10000, 'Connection timeout in ms'),
MONGODB_SOCKET_TIMEOUT: num(30000, 'Socket timeout in ms'),
MONGODB_SERVER_SELECTION_TIMEOUT: num(5000, 'Server selection timeout in ms'),
// SSL/TLS Settings
MONGODB_TLS: bool(false, 'Enable TLS for MongoDB connection'),
MONGODB_TLS_INSECURE: bool(false, 'Allow invalid certificates in TLS mode'),
MONGODB_TLS_CA_FILE: str('', 'Path to TLS CA certificate file'),
// Additional Settings
MONGODB_RETRY_WRITES: bool(true, 'Enable retryable writes'),
MONGODB_JOURNAL: bool(true, 'Enable write concern journal'),
MONGODB_READ_PREFERENCE: strWithChoices(['primary', 'primaryPreferred', 'secondary', 'secondaryPreferred', 'nearest'], 'primary', 'MongoDB read preference'),
MONGODB_WRITE_CONCERN: str('majority', '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;
/**
* MongoDB configuration using Yup
* Document storage for sentiment data, raw documents, and unstructured data
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, bool, num, strWithChoices } = envValidators;
/**
* MongoDB configuration with validation and defaults
*/
export const mongodbConfig = cleanEnv(process.env, {
// MongoDB Connection
MONGODB_HOST: str('localhost', 'MongoDB host'),
MONGODB_PORT: port(27017, 'MongoDB port'),
MONGODB_DATABASE: str('trading_documents', 'MongoDB database name'),
// Authentication
MONGODB_USERNAME: str('trading_admin', 'MongoDB username'),
MONGODB_PASSWORD: str('', 'MongoDB password'),
MONGODB_AUTH_SOURCE: str('admin', 'MongoDB authentication database'),
// Connection URI (alternative to individual settings)
MONGODB_URI: str('', 'Complete MongoDB connection URI (overrides individual settings)'),
// Connection Pool Settings
MONGODB_MAX_POOL_SIZE: num(10, 'Maximum connection pool size'),
MONGODB_MIN_POOL_SIZE: num(0, 'Minimum connection pool size'),
MONGODB_MAX_IDLE_TIME: num(30000, 'Maximum idle time for connections in ms'),
// Timeouts
MONGODB_CONNECT_TIMEOUT: num(10000, 'Connection timeout in ms'),
MONGODB_SOCKET_TIMEOUT: num(30000, 'Socket timeout in ms'),
MONGODB_SERVER_SELECTION_TIMEOUT: num(5000, 'Server selection timeout in ms'),
// SSL/TLS Settings
MONGODB_TLS: bool(false, 'Enable TLS for MongoDB connection'),
MONGODB_TLS_INSECURE: bool(false, 'Allow invalid certificates in TLS mode'),
MONGODB_TLS_CA_FILE: str('', 'Path to TLS CA certificate file'),
// Additional Settings
MONGODB_RETRY_WRITES: bool(true, 'Enable retryable writes'),
MONGODB_JOURNAL: bool(true, 'Enable write concern journal'),
MONGODB_READ_PREFERENCE: strWithChoices(
['primary', 'primaryPreferred', 'secondary', 'secondaryPreferred', 'nearest'],
'primary',
'MongoDB read preference'
),
MONGODB_WRITE_CONCERN: str('majority', '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;

View file

@ -1,88 +1,92 @@
/**
* Monitoring configuration using Yup
* Prometheus metrics, Grafana visualization, and Loki logging
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, bool, num, strWithChoices } = envValidators;
/**
* Prometheus configuration with validation and defaults
*/
export const prometheusConfig = cleanEnv(process.env, {
// Prometheus Server
PROMETHEUS_HOST: str('localhost', 'Prometheus host'),
PROMETHEUS_PORT: port(9090, 'Prometheus port'),
PROMETHEUS_URL: str('', 'Complete Prometheus URL (overrides host/port)'),
// Authentication
PROMETHEUS_USERNAME: str('', 'Prometheus username (if auth enabled)'),
PROMETHEUS_PASSWORD: str('', 'Prometheus password (if auth enabled)'),
// Metrics Collection
PROMETHEUS_SCRAPE_INTERVAL: str('15s', 'Default scrape interval'),
PROMETHEUS_EVALUATION_INTERVAL: str('15s', 'Rule evaluation interval'),
PROMETHEUS_RETENTION_TIME: str('15d', 'Data retention time'),
// TLS Settings
PROMETHEUS_TLS_ENABLED: bool(false, 'Enable TLS for Prometheus'),
PROMETHEUS_TLS_INSECURE: bool(false, 'Skip TLS verification'),
});
/**
* Grafana configuration with validation and defaults
*/
export const grafanaConfig = cleanEnv(process.env, {
// Grafana Server
GRAFANA_HOST: str('localhost', 'Grafana host'),
GRAFANA_PORT: port(3000, 'Grafana port'),
GRAFANA_URL: str('', 'Complete Grafana URL (overrides host/port)'),
// Authentication
GRAFANA_ADMIN_USER: str('admin', 'Grafana admin username'),
GRAFANA_ADMIN_PASSWORD: str('admin', 'Grafana admin password'),
// Security Settings
GRAFANA_ALLOW_SIGN_UP: bool(false, 'Allow user sign up'),
GRAFANA_SECRET_KEY: str('', 'Grafana secret key for encryption'),
// Database Settings
GRAFANA_DATABASE_TYPE: strWithChoices(['mysql', 'postgres', 'sqlite3'], 'sqlite3', 'Grafana database type'),
GRAFANA_DATABASE_URL: str('', 'Grafana database URL'),
// Feature Flags
GRAFANA_DISABLE_GRAVATAR: bool(true, 'Disable Gravatar avatars'),
GRAFANA_ENABLE_GZIP: bool(true, '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;
/**
* Monitoring configuration using Yup
* Prometheus metrics, Grafana visualization, and Loki logging
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, bool, num, strWithChoices } = envValidators;
/**
* Prometheus configuration with validation and defaults
*/
export const prometheusConfig = cleanEnv(process.env, {
// Prometheus Server
PROMETHEUS_HOST: str('localhost', 'Prometheus host'),
PROMETHEUS_PORT: port(9090, 'Prometheus port'),
PROMETHEUS_URL: str('', 'Complete Prometheus URL (overrides host/port)'),
// Authentication
PROMETHEUS_USERNAME: str('', 'Prometheus username (if auth enabled)'),
PROMETHEUS_PASSWORD: str('', 'Prometheus password (if auth enabled)'),
// Metrics Collection
PROMETHEUS_SCRAPE_INTERVAL: str('15s', 'Default scrape interval'),
PROMETHEUS_EVALUATION_INTERVAL: str('15s', 'Rule evaluation interval'),
PROMETHEUS_RETENTION_TIME: str('15d', 'Data retention time'),
// TLS Settings
PROMETHEUS_TLS_ENABLED: bool(false, 'Enable TLS for Prometheus'),
PROMETHEUS_TLS_INSECURE: bool(false, 'Skip TLS verification'),
});
/**
* Grafana configuration with validation and defaults
*/
export const grafanaConfig = cleanEnv(process.env, {
// Grafana Server
GRAFANA_HOST: str('localhost', 'Grafana host'),
GRAFANA_PORT: port(3000, 'Grafana port'),
GRAFANA_URL: str('', 'Complete Grafana URL (overrides host/port)'),
// Authentication
GRAFANA_ADMIN_USER: str('admin', 'Grafana admin username'),
GRAFANA_ADMIN_PASSWORD: str('admin', 'Grafana admin password'),
// Security Settings
GRAFANA_ALLOW_SIGN_UP: bool(false, 'Allow user sign up'),
GRAFANA_SECRET_KEY: str('', 'Grafana secret key for encryption'),
// Database Settings
GRAFANA_DATABASE_TYPE: strWithChoices(
['mysql', 'postgres', 'sqlite3'],
'sqlite3',
'Grafana database type'
),
GRAFANA_DATABASE_URL: str('', 'Grafana database URL'),
// Feature Flags
GRAFANA_DISABLE_GRAVATAR: bool(true, 'Disable Gravatar avatars'),
GRAFANA_ENABLE_GZIP: bool(true, '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;

View file

@ -1,56 +1,56 @@
/**
* PostgreSQL configuration using Yup
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, bool, num } = envValidators;
/**
* PostgreSQL configuration with validation and defaults
*/
export const postgresConfig = cleanEnv(process.env, {
// PostgreSQL Connection Settings
POSTGRES_HOST: str('localhost', 'PostgreSQL host'),
POSTGRES_PORT: port(5432, 'PostgreSQL port'),
POSTGRES_DATABASE: str('stockbot', 'PostgreSQL database name'),
POSTGRES_USERNAME: str('stockbot', 'PostgreSQL username'),
POSTGRES_PASSWORD: str('', 'PostgreSQL password'),
// Connection Pool Settings
POSTGRES_POOL_MIN: num(2, 'Minimum pool connections'),
POSTGRES_POOL_MAX: num(10, 'Maximum pool connections'),
POSTGRES_POOL_IDLE_TIMEOUT: num(30000, 'Pool idle timeout in ms'),
// SSL Configuration
POSTGRES_SSL: bool(false, 'Enable SSL for PostgreSQL connection'),
POSTGRES_SSL_REJECT_UNAUTHORIZED: bool(true, 'Reject unauthorized SSL certificates'),
// Additional Settings
POSTGRES_QUERY_TIMEOUT: num(30000, 'Query timeout in ms'),
POSTGRES_CONNECTION_TIMEOUT: num(5000, 'Connection timeout in ms'),
POSTGRES_STATEMENT_TIMEOUT: num(30000, 'Statement timeout in ms'),
POSTGRES_LOCK_TIMEOUT: num(10000, 'Lock timeout in ms'),
POSTGRES_IDLE_IN_TRANSACTION_SESSION_TIMEOUT: num(60000, 'Idle in transaction timeout in ms'),
});
// Export typed configuration object
export type PostgresConfig = typeof postgresConfig;
// Export individual config values for convenience
export const {
POSTGRES_HOST,
POSTGRES_PORT,
POSTGRES_DATABASE,
POSTGRES_USERNAME,
POSTGRES_PASSWORD,
POSTGRES_POOL_MIN,
POSTGRES_POOL_MAX,
POSTGRES_POOL_IDLE_TIMEOUT,
POSTGRES_SSL,
POSTGRES_SSL_REJECT_UNAUTHORIZED,
POSTGRES_QUERY_TIMEOUT,
POSTGRES_CONNECTION_TIMEOUT,
POSTGRES_STATEMENT_TIMEOUT,
POSTGRES_LOCK_TIMEOUT,
POSTGRES_IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
} = postgresConfig;
/**
* PostgreSQL configuration using Yup
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, bool, num } = envValidators;
/**
* PostgreSQL configuration with validation and defaults
*/
export const postgresConfig = cleanEnv(process.env, {
// PostgreSQL Connection Settings
POSTGRES_HOST: str('localhost', 'PostgreSQL host'),
POSTGRES_PORT: port(5432, 'PostgreSQL port'),
POSTGRES_DATABASE: str('stockbot', 'PostgreSQL database name'),
POSTGRES_USERNAME: str('stockbot', 'PostgreSQL username'),
POSTGRES_PASSWORD: str('', 'PostgreSQL password'),
// Connection Pool Settings
POSTGRES_POOL_MIN: num(2, 'Minimum pool connections'),
POSTGRES_POOL_MAX: num(10, 'Maximum pool connections'),
POSTGRES_POOL_IDLE_TIMEOUT: num(30000, 'Pool idle timeout in ms'),
// SSL Configuration
POSTGRES_SSL: bool(false, 'Enable SSL for PostgreSQL connection'),
POSTGRES_SSL_REJECT_UNAUTHORIZED: bool(true, 'Reject unauthorized SSL certificates'),
// Additional Settings
POSTGRES_QUERY_TIMEOUT: num(30000, 'Query timeout in ms'),
POSTGRES_CONNECTION_TIMEOUT: num(5000, 'Connection timeout in ms'),
POSTGRES_STATEMENT_TIMEOUT: num(30000, 'Statement timeout in ms'),
POSTGRES_LOCK_TIMEOUT: num(10000, 'Lock timeout in ms'),
POSTGRES_IDLE_IN_TRANSACTION_SESSION_TIMEOUT: num(60000, 'Idle in transaction timeout in ms'),
});
// Export typed configuration object
export type PostgresConfig = typeof postgresConfig;
// Export individual config values for convenience
export const {
POSTGRES_HOST,
POSTGRES_PORT,
POSTGRES_DATABASE,
POSTGRES_USERNAME,
POSTGRES_PASSWORD,
POSTGRES_POOL_MIN,
POSTGRES_POOL_MAX,
POSTGRES_POOL_IDLE_TIMEOUT,
POSTGRES_SSL,
POSTGRES_SSL_REJECT_UNAUTHORIZED,
POSTGRES_QUERY_TIMEOUT,
POSTGRES_CONNECTION_TIMEOUT,
POSTGRES_STATEMENT_TIMEOUT,
POSTGRES_LOCK_TIMEOUT,
POSTGRES_IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
} = postgresConfig;

View file

@ -1,55 +1,55 @@
/**
* QuestDB configuration using Yup
* Time-series database for OHLCV data, indicators, and performance metrics
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, bool, num } = envValidators;
/**
* QuestDB configuration with validation and defaults
*/
export const questdbConfig = cleanEnv(process.env, {
// QuestDB Connection
QUESTDB_HOST: str('localhost', 'QuestDB host'),
QUESTDB_HTTP_PORT: port(9000, 'QuestDB HTTP port (web console)'),
QUESTDB_PG_PORT: port(8812, 'QuestDB PostgreSQL wire protocol port'),
QUESTDB_INFLUX_PORT: port(9009, 'QuestDB InfluxDB line protocol port'),
// Authentication (if enabled)
QUESTDB_USER: str('', 'QuestDB username (if auth enabled)'),
QUESTDB_PASSWORD: str('', 'QuestDB password (if auth enabled)'),
// Connection Settings
QUESTDB_CONNECTION_TIMEOUT: num(5000, 'Connection timeout in ms'),
QUESTDB_REQUEST_TIMEOUT: num(30000, 'Request timeout in ms'),
QUESTDB_RETRY_ATTEMPTS: num(3, 'Number of retry attempts'),
// TLS Settings
QUESTDB_TLS_ENABLED: bool(false, 'Enable TLS for QuestDB connection'),
QUESTDB_TLS_VERIFY_SERVER_CERT: bool(true, 'Verify server certificate'),
// Database Settings
QUESTDB_DEFAULT_DATABASE: str('qdb', 'Default database name'),
QUESTDB_TELEMETRY_ENABLED: bool(false, '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;
/**
* QuestDB configuration using Yup
* Time-series database for OHLCV data, indicators, and performance metrics
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, port, bool, num } = envValidators;
/**
* QuestDB configuration with validation and defaults
*/
export const questdbConfig = cleanEnv(process.env, {
// QuestDB Connection
QUESTDB_HOST: str('localhost', 'QuestDB host'),
QUESTDB_HTTP_PORT: port(9000, 'QuestDB HTTP port (web console)'),
QUESTDB_PG_PORT: port(8812, 'QuestDB PostgreSQL wire protocol port'),
QUESTDB_INFLUX_PORT: port(9009, 'QuestDB InfluxDB line protocol port'),
// Authentication (if enabled)
QUESTDB_USER: str('', 'QuestDB username (if auth enabled)'),
QUESTDB_PASSWORD: str('', 'QuestDB password (if auth enabled)'),
// Connection Settings
QUESTDB_CONNECTION_TIMEOUT: num(5000, 'Connection timeout in ms'),
QUESTDB_REQUEST_TIMEOUT: num(30000, 'Request timeout in ms'),
QUESTDB_RETRY_ATTEMPTS: num(3, 'Number of retry attempts'),
// TLS Settings
QUESTDB_TLS_ENABLED: bool(false, 'Enable TLS for QuestDB connection'),
QUESTDB_TLS_VERIFY_SERVER_CERT: bool(true, 'Verify server certificate'),
// Database Settings
QUESTDB_DEFAULT_DATABASE: str('qdb', 'Default database name'),
QUESTDB_TELEMETRY_ENABLED: bool(false, '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;

View file

@ -1,80 +1,80 @@
/**
* Risk management configuration using Yup
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, num, bool, strWithChoices } = envValidators;
/**
* Risk configuration with validation and defaults
*/
export const riskConfig = cleanEnv(process.env, {
// Position Sizing
RISK_MAX_POSITION_SIZE: num(0.1, 'Maximum position size as percentage of portfolio'),
RISK_MAX_PORTFOLIO_EXPOSURE: num(0.8, 'Maximum portfolio exposure percentage'),
RISK_MAX_SINGLE_ASSET_EXPOSURE: num(0.2, 'Maximum exposure to single asset'),
RISK_MAX_SECTOR_EXPOSURE: num(0.3, 'Maximum exposure to single sector'),
// Stop Loss and Take Profit
RISK_DEFAULT_STOP_LOSS: num(0.05, 'Default stop loss percentage'),
RISK_DEFAULT_TAKE_PROFIT: num(0.15, 'Default take profit percentage'),
RISK_TRAILING_STOP_ENABLED: bool(true, 'Enable trailing stop losses'),
RISK_TRAILING_STOP_DISTANCE: num(0.03, 'Trailing stop distance percentage'),
// Risk Limits
RISK_MAX_DAILY_LOSS: num(0.05, 'Maximum daily loss percentage'),
RISK_MAX_WEEKLY_LOSS: num(0.1, 'Maximum weekly loss percentage'),
RISK_MAX_MONTHLY_LOSS: num(0.2, 'Maximum monthly loss percentage'),
// Volatility Controls
RISK_MAX_VOLATILITY_THRESHOLD: num(0.4, 'Maximum volatility threshold'),
RISK_VOLATILITY_LOOKBACK_DAYS: num(20, 'Volatility calculation lookback period'),
// Correlation Controls
RISK_MAX_CORRELATION_THRESHOLD: num(0.7, 'Maximum correlation between positions'),
RISK_CORRELATION_LOOKBACK_DAYS: num(60, 'Correlation calculation lookback period'),
// Leverage Controls
RISK_MAX_LEVERAGE: num(2.0, 'Maximum leverage allowed'),
RISK_MARGIN_CALL_THRESHOLD: num(0.3, 'Margin call threshold'),
// Circuit Breakers
RISK_CIRCUIT_BREAKER_ENABLED: bool(true, 'Enable circuit breakers'),
RISK_CIRCUIT_BREAKER_LOSS_THRESHOLD: num(0.1, 'Circuit breaker loss threshold'),
RISK_CIRCUIT_BREAKER_COOLDOWN_MINUTES: num(60, 'Circuit breaker cooldown period'),
// Risk Model
RISK_MODEL_TYPE: strWithChoices(['var', 'cvar', 'expected_shortfall'], 'var', 'Risk model type'),
RISK_CONFIDENCE_LEVEL: num(0.95, 'Risk model confidence level'),
RISK_TIME_HORIZON_DAYS: num(1, 'Risk time horizon in days'),
});
// Export typed configuration object
export type RiskConfig = typeof riskConfig;
// 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;
/**
* Risk management configuration using Yup
*/
import { cleanEnv, envValidators } from './env-utils';
const { str, num, bool, strWithChoices } = envValidators;
/**
* Risk configuration with validation and defaults
*/
export const riskConfig = cleanEnv(process.env, {
// Position Sizing
RISK_MAX_POSITION_SIZE: num(0.1, 'Maximum position size as percentage of portfolio'),
RISK_MAX_PORTFOLIO_EXPOSURE: num(0.8, 'Maximum portfolio exposure percentage'),
RISK_MAX_SINGLE_ASSET_EXPOSURE: num(0.2, 'Maximum exposure to single asset'),
RISK_MAX_SECTOR_EXPOSURE: num(0.3, 'Maximum exposure to single sector'),
// Stop Loss and Take Profit
RISK_DEFAULT_STOP_LOSS: num(0.05, 'Default stop loss percentage'),
RISK_DEFAULT_TAKE_PROFIT: num(0.15, 'Default take profit percentage'),
RISK_TRAILING_STOP_ENABLED: bool(true, 'Enable trailing stop losses'),
RISK_TRAILING_STOP_DISTANCE: num(0.03, 'Trailing stop distance percentage'),
// Risk Limits
RISK_MAX_DAILY_LOSS: num(0.05, 'Maximum daily loss percentage'),
RISK_MAX_WEEKLY_LOSS: num(0.1, 'Maximum weekly loss percentage'),
RISK_MAX_MONTHLY_LOSS: num(0.2, 'Maximum monthly loss percentage'),
// Volatility Controls
RISK_MAX_VOLATILITY_THRESHOLD: num(0.4, 'Maximum volatility threshold'),
RISK_VOLATILITY_LOOKBACK_DAYS: num(20, 'Volatility calculation lookback period'),
// Correlation Controls
RISK_MAX_CORRELATION_THRESHOLD: num(0.7, 'Maximum correlation between positions'),
RISK_CORRELATION_LOOKBACK_DAYS: num(60, 'Correlation calculation lookback period'),
// Leverage Controls
RISK_MAX_LEVERAGE: num(2.0, 'Maximum leverage allowed'),
RISK_MARGIN_CALL_THRESHOLD: num(0.3, 'Margin call threshold'),
// Circuit Breakers
RISK_CIRCUIT_BREAKER_ENABLED: bool(true, 'Enable circuit breakers'),
RISK_CIRCUIT_BREAKER_LOSS_THRESHOLD: num(0.1, 'Circuit breaker loss threshold'),
RISK_CIRCUIT_BREAKER_COOLDOWN_MINUTES: num(60, 'Circuit breaker cooldown period'),
// Risk Model
RISK_MODEL_TYPE: strWithChoices(['var', 'cvar', 'expected_shortfall'], 'var', 'Risk model type'),
RISK_CONFIDENCE_LEVEL: num(0.95, 'Risk model confidence level'),
RISK_TIME_HORIZON_DAYS: num(1, 'Risk time horizon in days'),
});
// Export typed configuration object
export type RiskConfig = typeof riskConfig;
// 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;

View file

@ -1,433 +1,445 @@
/**
* Integration Tests for Config Library
*
* Tests the entire configuration system including module interactions,
* environment loading, validation across modules, and type exports.
*/
import { describe, test, expect, beforeEach } from 'bun:test';
import { setTestEnv, clearEnvVars, getMinimalTestEnv } from '../test/setup';
describe('Config Library Integration', () => {
beforeEach(() => {
// Clear module cache for clean state
// Note: Bun handles module caching differently than Jest
});
describe('Complete Configuration Loading', () => { test('should load all configuration modules successfully', async () => {
setTestEnv(getMinimalTestEnv());
// Import all modules
const [
{ Environment, getEnvironment },
{ postgresConfig },
{ questdbConfig },
{ mongodbConfig },
{ loggingConfig },
{ riskConfig }
] = await Promise.all([
import('../src/core'),
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb'),
import('../src/logging'),
import('../src/risk')
]);
// Verify all configs are loaded
expect(Environment).toBeDefined();
expect(getEnvironment).toBeDefined();
expect(postgresConfig).toBeDefined();
expect(questdbConfig).toBeDefined();
expect(mongodbConfig).toBeDefined();
expect(loggingConfig).toBeDefined();
expect(riskConfig).toBeDefined();
// Verify core utilities
expect(getEnvironment()).toBe(Environment.Testing); // Should be Testing due to NODE_ENV=test in setup
expect(postgresConfig.POSTGRES_HOST).toBe('localhost'); expect(questdbConfig.QUESTDB_HOST).toBe('localhost');
expect(mongodbConfig.MONGODB_HOST).toBe('localhost'); // fix: use correct property
expect(loggingConfig.LOG_LEVEL).toBeDefined();
expect(riskConfig.RISK_MAX_POSITION_SIZE).toBe(0.1);
}); test('should handle missing required environment variables gracefully', async () => {
setTestEnv({
NODE_ENV: 'test'
// Missing required variables
});
// Should be able to load core utilities
const { Environment, getEnvironment } = await import('../src/core');
expect(Environment).toBeDefined();
expect(getEnvironment()).toBe(Environment.Testing);
// Should fail to load modules requiring specific vars (if they have required vars)
// Note: Most modules have defaults, so they might not throw
try {
const { postgresConfig } = await import('../src/postgres');
expect(postgresConfig).toBeDefined();
expect(postgresConfig.POSTGRES_HOST).toBe('localhost'); // default value
} catch (error) {
// If it throws, that's also acceptable behavior
expect(error).toBeDefined();
}
}); test('should maintain consistency across environment detection', async () => {
setTestEnv({
NODE_ENV: 'production',
...getMinimalTestEnv()
});
const [
{ Environment, getEnvironment },
{ postgresConfig },
{ questdbConfig },
{ mongodbConfig },
{ loggingConfig }
] = await Promise.all([
import('../src/core'),
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb'),
import('../src/logging')
]);
// Note: Due to module caching, environment is set at first import
// All modules should detect the same environment (which will be Testing due to test setup)
expect(getEnvironment()).toBe(Environment.Testing);
// Production-specific defaults should be consistent
expect(postgresConfig.POSTGRES_SSL).toBe(false); // default is false unless overridden expect(questdbConfig.QUESTDB_TLS_ENABLED).toBe(false); // checking actual property name
expect(mongodbConfig.MONGODB_TLS).toBe(false); // checking actual property name
expect(loggingConfig.LOG_FORMAT).toBe('json');
});
});
describe('Main Index Exports', () => { test('should export all configuration objects from index', async () => {
setTestEnv(getMinimalTestEnv());
const config = await import('../src/index');
// Core utilities (no coreConfig object)
expect(config.Environment).toBeDefined();
expect(config.getEnvironment).toBeDefined();
expect(config.ConfigurationError).toBeDefined();
// Configuration objects
expect(config.postgresConfig).toBeDefined();
expect(config.questdbConfig).toBeDefined();
expect(config.mongodbConfig).toBeDefined();
expect(config.loggingConfig).toBeDefined();
expect(config.riskConfig).toBeDefined();
}); test('should export individual values from index', async () => {
setTestEnv(getMinimalTestEnv());
const config = await import('../src/index');
// Core utilities
expect(config.Environment).toBeDefined();
expect(config.getEnvironment).toBeDefined();
// Individual configuration values exported from modules
expect(config.POSTGRES_HOST).toBeDefined();
expect(config.POSTGRES_PORT).toBeDefined();
expect(config.QUESTDB_HOST).toBeDefined();
expect(config.MONGODB_HOST).toBeDefined();
// Risk values
expect(config.RISK_MAX_POSITION_SIZE).toBeDefined();
expect(config.RISK_MAX_DAILY_LOSS).toBeDefined();
// Logging values
expect(config.LOG_LEVEL).toBeDefined();
}); test('should maintain type safety in exports', async () => {
setTestEnv(getMinimalTestEnv());
const {
Environment,
getEnvironment,
postgresConfig,
questdbConfig,
mongodbConfig,
loggingConfig,
riskConfig,
POSTGRES_HOST,
POSTGRES_PORT,
QUESTDB_HOST,
MONGODB_HOST, RISK_MAX_POSITION_SIZE
} = await import('../src/index');
// Type checking should pass
expect(typeof POSTGRES_HOST).toBe('string');
expect(typeof POSTGRES_PORT).toBe('number');
expect(typeof QUESTDB_HOST).toBe('string');
expect(typeof MONGODB_HOST).toBe('string');
expect(typeof RISK_MAX_POSITION_SIZE).toBe('number');
// Configuration objects should have expected shapes
expect(postgresConfig).toHaveProperty('POSTGRES_HOST');
expect(postgresConfig).toHaveProperty('POSTGRES_PORT');
expect(questdbConfig).toHaveProperty('QUESTDB_HOST');
expect(mongodbConfig).toHaveProperty('MONGODB_HOST');
expect(loggingConfig).toHaveProperty('LOG_LEVEL');
expect(riskConfig).toHaveProperty('RISK_MAX_POSITION_SIZE');
});
});
describe('Environment Variable Validation', () => {
test('should validate environment variables across all modules', async () => {
setTestEnv({
NODE_ENV: 'test',
LOG_LEVEL: 'info', // valid level
POSTGRES_HOST: 'localhost',
POSTGRES_DATABASE: 'test',
POSTGRES_USERNAME: 'test',
POSTGRES_PASSWORD: 'test',
QUESTDB_HOST: 'localhost',
MONGODB_HOST: 'localhost',
MONGODB_DATABASE: 'test',
RISK_MAX_POSITION_SIZE: '0.1',
RISK_MAX_DAILY_LOSS: '0.05'
}); // All imports should succeed with valid config
const [core, postgres, questdb, mongodb, logging, risk] = await Promise.all([
import('../src/core'),
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb'),
import('../src/logging'),
import('../src/risk')
]);
expect(core.getEnvironment()).toBe(core.Environment.Testing); // default test env
expect(postgres.postgresConfig.POSTGRES_HOST).toBe('localhost');
expect(questdb.questdbConfig.QUESTDB_HOST).toBe('localhost');
expect(mongodb.mongodbConfig.MONGODB_HOST).toBe('localhost');
expect(logging.loggingConfig.LOG_LEVEL).toBe('info'); // set in test
expect(risk.riskConfig.RISK_MAX_POSITION_SIZE).toBe(0.1); // from test env
}); test('should accept valid environment variables across all modules', async () => {
setTestEnv({
NODE_ENV: 'development',
LOG_LEVEL: 'debug',
POSTGRES_HOST: 'localhost',
POSTGRES_PORT: '5432',
POSTGRES_DATABASE: 'stockbot_dev',
POSTGRES_USERNAME: 'dev_user',
POSTGRES_PASSWORD: 'dev_pass',
POSTGRES_SSL: 'false',
QUESTDB_HOST: 'localhost',
QUESTDB_HTTP_PORT: '9000',
QUESTDB_PG_PORT: '8812',
MONGODB_HOST: 'localhost',
MONGODB_DATABASE: 'stockbot_dev',
RISK_MAX_POSITION_SIZE: '0.25',
RISK_MAX_DAILY_LOSS: '0.025',
LOG_FORMAT: 'json',
LOG_FILE_ENABLED: 'false'
});
// All imports should succeed
const [core, postgres, questdb, mongodb, logging, risk] = await Promise.all([
import('../src/core'),
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb'),
import('../src/logging'),
import('../src/risk')
]);
// Since this is the first test to set NODE_ENV to development and modules might not be cached yet,
// this could actually change the environment. Let's test what we actually get.
expect(core.getEnvironment()).toBeDefined(); // Just verify it returns something valid
expect(postgres.postgresConfig.POSTGRES_HOST).toBe('localhost');
expect(questdb.questdbConfig.QUESTDB_HOST).toBe('localhost');
expect(mongodb.mongodbConfig.MONGODB_HOST).toBe('localhost');
expect(logging.loggingConfig.LOG_FORMAT).toBe('json'); // default value
expect(risk.riskConfig.RISK_MAX_POSITION_SIZE).toBe(0.1); // default value
});
});
describe('Configuration Consistency', () => { test('should maintain consistent SSL settings across databases', async () => {
setTestEnv({
NODE_ENV: 'production',
POSTGRES_HOST: 'prod-postgres.com',
POSTGRES_DATABASE: 'prod_db',
POSTGRES_USERNAME: 'prod_user',
POSTGRES_PASSWORD: 'prod_pass',
QUESTDB_HOST: 'prod-questdb.com',
MONGODB_HOST: 'prod-mongo.com',
MONGODB_DATABASE: 'prod_db',
RISK_MAX_POSITION_SIZE: '0.1',
RISK_MAX_DAILY_LOSS: '0.05'
// SSL settings not explicitly set - should use defaults
});
const [postgres, questdb, mongodb] = await Promise.all([
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb')
]);
// Check actual SSL property names and their default values expect(postgres.postgresConfig.POSTGRES_SSL).toBe(false); // default is false
expect(questdb.questdbConfig.QUESTDB_TLS_ENABLED).toBe(false); // default is false
expect(mongodb.mongodbConfig.MONGODB_TLS).toBe(false); // default is false
}); test('should maintain consistent environment detection across modules', async () => {
setTestEnv({
NODE_ENV: 'staging',
...getMinimalTestEnv()
});
const [core, logging] = await Promise.all([
import('../src/core'),
import('../src/logging')
]);
expect(core.getEnvironment()).toBe(core.Environment.Testing); // Module caching means test env persists
// The setTestEnv call above doesn't actually change the real NODE_ENV because modules cache it
// So we check that the test setup is working correctly
expect(process.env.NODE_ENV).toBe('test'); // This is what's actually set in test environment
});
});
describe('Performance and Caching', () => { test('should cache configuration values between imports', async () => {
setTestEnv(getMinimalTestEnv());
// Import the same module multiple times
const postgres1 = await import('../src/postgres');
const postgres2 = await import('../src/postgres');
const postgres3 = await import('../src/postgres');
// Should return the same object reference (cached)
expect(postgres1.postgresConfig).toBe(postgres2.postgresConfig);
expect(postgres2.postgresConfig).toBe(postgres3.postgresConfig);
});
test('should handle rapid sequential imports', async () => {
setTestEnv(getMinimalTestEnv());
// Import all modules simultaneously
const startTime = Date.now();
await Promise.all([
import('../src/core'),
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb'),
import('../src/logging'),
import('../src/risk')
]);
const endTime = Date.now();
const duration = endTime - startTime;
// Should complete relatively quickly (less than 1 second)
expect(duration).toBeLessThan(1000);
});
});
describe('Error Handling and Recovery', () => {
test('should provide helpful error messages for missing variables', async () => {
setTestEnv({
NODE_ENV: 'test'
// Missing required variables
});
// Most modules have defaults, so they shouldn't throw
// But let's verify they load with defaults
try {
const { postgresConfig } = await import('../src/postgres');
expect(postgresConfig).toBeDefined();
expect(postgresConfig.POSTGRES_HOST).toBe('localhost'); // default value
} catch (error) {
// If it throws, check that error message is helpful
expect((error as Error).message).toBeTruthy();
}
try {
const { riskConfig } = await import('../src/risk');
expect(riskConfig).toBeDefined();
expect(riskConfig.RISK_MAX_POSITION_SIZE).toBe(0.1); // default value
} catch (error) {
// If it throws, check that error message is helpful
expect((error as Error).message).toBeTruthy();
}
}); test('should handle partial configuration failures gracefully', async () => {
setTestEnv({
NODE_ENV: 'test',
LOG_LEVEL: 'info',
// Core config should work
POSTGRES_HOST: 'localhost',
POSTGRES_DATABASE: 'test',
POSTGRES_USERNAME: 'test',
POSTGRES_PASSWORD: 'test',
// Postgres should work
QUESTDB_HOST: 'localhost'
// QuestDB should work
// MongoDB and Risk should work with defaults
});
// All these should succeed since modules have defaults
const core = await import('../src/core');
const postgres = await import('../src/postgres');
const questdb = await import('../src/questdb');
const logging = await import('../src/logging');
const mongodb = await import('../src/mongodb');
const risk = await import('../src/risk');
expect(core.Environment).toBeDefined();
expect(postgres.postgresConfig).toBeDefined();
expect(questdb.questdbConfig).toBeDefined();
expect(logging.loggingConfig).toBeDefined();
expect(mongodb.mongodbConfig).toBeDefined();
expect(risk.riskConfig).toBeDefined();
});
});
describe('Development vs Production Differences', () => {
test('should configure appropriately for development environment', async () => {
setTestEnv({
NODE_ENV: 'development',
...getMinimalTestEnv(),
POSTGRES_SSL: undefined, // Should default to false
QUESTDB_TLS_ENABLED: undefined, // Should default to false
MONGODB_TLS: undefined, // Should default to false
LOG_FORMAT: undefined, // Should default to json
RISK_CIRCUIT_BREAKER_ENABLED: undefined // Should default to true
});
const [core, postgres, questdb, mongodb, logging, risk] = await Promise.all([
import('../src/core'),
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb'),
import('../src/logging'),
import('../src/risk')
]);
expect(core.getEnvironment()).toBe(core.Environment.Testing); // Module caching means test env persists
expect(postgres.postgresConfig.POSTGRES_SSL).toBe(false);
expect(questdb.questdbConfig.QUESTDB_TLS_ENABLED).toBe(false); expect(mongodb.mongodbConfig.MONGODB_TLS).toBe(false);
expect(logging.loggingConfig.LOG_FORMAT).toBe('json'); // default
expect(risk.riskConfig.RISK_CIRCUIT_BREAKER_ENABLED).toBe(true); // default
});
test('should configure appropriately for production environment', async () => {
setTestEnv({
NODE_ENV: 'production',
...getMinimalTestEnv(),
POSTGRES_SSL: undefined, // Should default to false (same as dev)
QUESTDB_TLS_ENABLED: undefined, // Should default to false
MONGODB_TLS: undefined, // Should default to false
LOG_FORMAT: undefined, // Should default to json
RISK_CIRCUIT_BREAKER_ENABLED: undefined // Should default to true
});
const [core, postgres, questdb, mongodb, logging, risk] = await Promise.all([
import('../src/core'),
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb'),
import('../src/logging'),
import('../src/risk') ]);
expect(core.getEnvironment()).toBe(core.Environment.Testing); // Module caching means test env persists
expect(postgres.postgresConfig.POSTGRES_SSL).toBe(false); // default doesn't change by env
expect(questdb.questdbConfig.QUESTDB_TLS_ENABLED).toBe(false);
expect(mongodb.mongodbConfig.MONGODB_TLS).toBe(false);
expect(logging.loggingConfig.LOG_FORMAT).toBe('json');
expect(risk.riskConfig.RISK_CIRCUIT_BREAKER_ENABLED).toBe(true);
});
});
});
/**
* Integration Tests for Config Library
*
* Tests the entire configuration system including module interactions,
* environment loading, validation across modules, and type exports.
*/
import { beforeEach, describe, expect, test } from 'bun:test';
import { clearEnvVars, getMinimalTestEnv, setTestEnv } from '../test/setup';
describe('Config Library Integration', () => {
beforeEach(() => {
// Clear module cache for clean state
// Note: Bun handles module caching differently than Jest
});
describe('Complete Configuration Loading', () => {
test('should load all configuration modules successfully', async () => {
setTestEnv(getMinimalTestEnv());
// Import all modules
const [
{ Environment, getEnvironment },
{ postgresConfig },
{ questdbConfig },
{ mongodbConfig },
{ loggingConfig },
{ riskConfig },
] = await Promise.all([
import('../src/core'),
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb'),
import('../src/logging'),
import('../src/risk'),
]);
// Verify all configs are loaded
expect(Environment).toBeDefined();
expect(getEnvironment).toBeDefined();
expect(postgresConfig).toBeDefined();
expect(questdbConfig).toBeDefined();
expect(mongodbConfig).toBeDefined();
expect(loggingConfig).toBeDefined();
expect(riskConfig).toBeDefined();
// Verify core utilities
expect(getEnvironment()).toBe(Environment.Testing); // Should be Testing due to NODE_ENV=test in setup
expect(postgresConfig.POSTGRES_HOST).toBe('localhost');
expect(questdbConfig.QUESTDB_HOST).toBe('localhost');
expect(mongodbConfig.MONGODB_HOST).toBe('localhost'); // fix: use correct property
expect(loggingConfig.LOG_LEVEL).toBeDefined();
expect(riskConfig.RISK_MAX_POSITION_SIZE).toBe(0.1);
});
test('should handle missing required environment variables gracefully', async () => {
setTestEnv({
NODE_ENV: 'test',
// Missing required variables
});
// Should be able to load core utilities
const { Environment, getEnvironment } = await import('../src/core');
expect(Environment).toBeDefined();
expect(getEnvironment()).toBe(Environment.Testing);
// Should fail to load modules requiring specific vars (if they have required vars)
// Note: Most modules have defaults, so they might not throw
try {
const { postgresConfig } = await import('../src/postgres');
expect(postgresConfig).toBeDefined();
expect(postgresConfig.POSTGRES_HOST).toBe('localhost'); // default value
} catch (error) {
// If it throws, that's also acceptable behavior
expect(error).toBeDefined();
}
});
test('should maintain consistency across environment detection', async () => {
setTestEnv({
NODE_ENV: 'production',
...getMinimalTestEnv(),
});
const [
{ Environment, getEnvironment },
{ postgresConfig },
{ questdbConfig },
{ mongodbConfig },
{ loggingConfig },
] = await Promise.all([
import('../src/core'),
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb'),
import('../src/logging'),
]);
// Note: Due to module caching, environment is set at first import
// All modules should detect the same environment (which will be Testing due to test setup)
expect(getEnvironment()).toBe(Environment.Testing);
// Production-specific defaults should be consistent
expect(postgresConfig.POSTGRES_SSL).toBe(false); // default is false unless overridden expect(questdbConfig.QUESTDB_TLS_ENABLED).toBe(false); // checking actual property name
expect(mongodbConfig.MONGODB_TLS).toBe(false); // checking actual property name
expect(loggingConfig.LOG_FORMAT).toBe('json');
});
});
describe('Main Index Exports', () => {
test('should export all configuration objects from index', async () => {
setTestEnv(getMinimalTestEnv());
const config = await import('../src/index');
// Core utilities (no coreConfig object)
expect(config.Environment).toBeDefined();
expect(config.getEnvironment).toBeDefined();
expect(config.ConfigurationError).toBeDefined();
// Configuration objects
expect(config.postgresConfig).toBeDefined();
expect(config.questdbConfig).toBeDefined();
expect(config.mongodbConfig).toBeDefined();
expect(config.loggingConfig).toBeDefined();
expect(config.riskConfig).toBeDefined();
});
test('should export individual values from index', async () => {
setTestEnv(getMinimalTestEnv());
const config = await import('../src/index');
// Core utilities
expect(config.Environment).toBeDefined();
expect(config.getEnvironment).toBeDefined();
// Individual configuration values exported from modules
expect(config.POSTGRES_HOST).toBeDefined();
expect(config.POSTGRES_PORT).toBeDefined();
expect(config.QUESTDB_HOST).toBeDefined();
expect(config.MONGODB_HOST).toBeDefined();
// Risk values
expect(config.RISK_MAX_POSITION_SIZE).toBeDefined();
expect(config.RISK_MAX_DAILY_LOSS).toBeDefined();
// Logging values
expect(config.LOG_LEVEL).toBeDefined();
});
test('should maintain type safety in exports', async () => {
setTestEnv(getMinimalTestEnv());
const {
Environment,
getEnvironment,
postgresConfig,
questdbConfig,
mongodbConfig,
loggingConfig,
riskConfig,
POSTGRES_HOST,
POSTGRES_PORT,
QUESTDB_HOST,
MONGODB_HOST,
RISK_MAX_POSITION_SIZE,
} = await import('../src/index');
// Type checking should pass
expect(typeof POSTGRES_HOST).toBe('string');
expect(typeof POSTGRES_PORT).toBe('number');
expect(typeof QUESTDB_HOST).toBe('string');
expect(typeof MONGODB_HOST).toBe('string');
expect(typeof RISK_MAX_POSITION_SIZE).toBe('number');
// Configuration objects should have expected shapes
expect(postgresConfig).toHaveProperty('POSTGRES_HOST');
expect(postgresConfig).toHaveProperty('POSTGRES_PORT');
expect(questdbConfig).toHaveProperty('QUESTDB_HOST');
expect(mongodbConfig).toHaveProperty('MONGODB_HOST');
expect(loggingConfig).toHaveProperty('LOG_LEVEL');
expect(riskConfig).toHaveProperty('RISK_MAX_POSITION_SIZE');
});
});
describe('Environment Variable Validation', () => {
test('should validate environment variables across all modules', async () => {
setTestEnv({
NODE_ENV: 'test',
LOG_LEVEL: 'info', // valid level
POSTGRES_HOST: 'localhost',
POSTGRES_DATABASE: 'test',
POSTGRES_USERNAME: 'test',
POSTGRES_PASSWORD: 'test',
QUESTDB_HOST: 'localhost',
MONGODB_HOST: 'localhost',
MONGODB_DATABASE: 'test',
RISK_MAX_POSITION_SIZE: '0.1',
RISK_MAX_DAILY_LOSS: '0.05',
}); // All imports should succeed with valid config
const [core, postgres, questdb, mongodb, logging, risk] = await Promise.all([
import('../src/core'),
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb'),
import('../src/logging'),
import('../src/risk'),
]);
expect(core.getEnvironment()).toBe(core.Environment.Testing); // default test env
expect(postgres.postgresConfig.POSTGRES_HOST).toBe('localhost');
expect(questdb.questdbConfig.QUESTDB_HOST).toBe('localhost');
expect(mongodb.mongodbConfig.MONGODB_HOST).toBe('localhost');
expect(logging.loggingConfig.LOG_LEVEL).toBe('info'); // set in test
expect(risk.riskConfig.RISK_MAX_POSITION_SIZE).toBe(0.1); // from test env
});
test('should accept valid environment variables across all modules', async () => {
setTestEnv({
NODE_ENV: 'development',
LOG_LEVEL: 'debug',
POSTGRES_HOST: 'localhost',
POSTGRES_PORT: '5432',
POSTGRES_DATABASE: 'stockbot_dev',
POSTGRES_USERNAME: 'dev_user',
POSTGRES_PASSWORD: 'dev_pass',
POSTGRES_SSL: 'false',
QUESTDB_HOST: 'localhost',
QUESTDB_HTTP_PORT: '9000',
QUESTDB_PG_PORT: '8812',
MONGODB_HOST: 'localhost',
MONGODB_DATABASE: 'stockbot_dev',
RISK_MAX_POSITION_SIZE: '0.25',
RISK_MAX_DAILY_LOSS: '0.025',
LOG_FORMAT: 'json',
LOG_FILE_ENABLED: 'false',
});
// All imports should succeed
const [core, postgres, questdb, mongodb, logging, risk] = await Promise.all([
import('../src/core'),
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb'),
import('../src/logging'),
import('../src/risk'),
]);
// Since this is the first test to set NODE_ENV to development and modules might not be cached yet,
// this could actually change the environment. Let's test what we actually get.
expect(core.getEnvironment()).toBeDefined(); // Just verify it returns something valid
expect(postgres.postgresConfig.POSTGRES_HOST).toBe('localhost');
expect(questdb.questdbConfig.QUESTDB_HOST).toBe('localhost');
expect(mongodb.mongodbConfig.MONGODB_HOST).toBe('localhost');
expect(logging.loggingConfig.LOG_FORMAT).toBe('json'); // default value
expect(risk.riskConfig.RISK_MAX_POSITION_SIZE).toBe(0.1); // default value
});
});
describe('Configuration Consistency', () => {
test('should maintain consistent SSL settings across databases', async () => {
setTestEnv({
NODE_ENV: 'production',
POSTGRES_HOST: 'prod-postgres.com',
POSTGRES_DATABASE: 'prod_db',
POSTGRES_USERNAME: 'prod_user',
POSTGRES_PASSWORD: 'prod_pass',
QUESTDB_HOST: 'prod-questdb.com',
MONGODB_HOST: 'prod-mongo.com',
MONGODB_DATABASE: 'prod_db',
RISK_MAX_POSITION_SIZE: '0.1',
RISK_MAX_DAILY_LOSS: '0.05',
// SSL settings not explicitly set - should use defaults
});
const [postgres, questdb, mongodb] = await Promise.all([
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb'),
]);
// Check actual SSL property names and their default values expect(postgres.postgresConfig.POSTGRES_SSL).toBe(false); // default is false
expect(questdb.questdbConfig.QUESTDB_TLS_ENABLED).toBe(false); // default is false
expect(mongodb.mongodbConfig.MONGODB_TLS).toBe(false); // default is false
});
test('should maintain consistent environment detection across modules', async () => {
setTestEnv({
NODE_ENV: 'staging',
...getMinimalTestEnv(),
});
const [core, logging] = await Promise.all([import('../src/core'), import('../src/logging')]);
expect(core.getEnvironment()).toBe(core.Environment.Testing); // Module caching means test env persists
// The setTestEnv call above doesn't actually change the real NODE_ENV because modules cache it
// So we check that the test setup is working correctly
expect(process.env.NODE_ENV).toBe('test'); // This is what's actually set in test environment
});
});
describe('Performance and Caching', () => {
test('should cache configuration values between imports', async () => {
setTestEnv(getMinimalTestEnv());
// Import the same module multiple times
const postgres1 = await import('../src/postgres');
const postgres2 = await import('../src/postgres');
const postgres3 = await import('../src/postgres');
// Should return the same object reference (cached)
expect(postgres1.postgresConfig).toBe(postgres2.postgresConfig);
expect(postgres2.postgresConfig).toBe(postgres3.postgresConfig);
});
test('should handle rapid sequential imports', async () => {
setTestEnv(getMinimalTestEnv());
// Import all modules simultaneously
const startTime = Date.now();
await Promise.all([
import('../src/core'),
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb'),
import('../src/logging'),
import('../src/risk'),
]);
const endTime = Date.now();
const duration = endTime - startTime;
// Should complete relatively quickly (less than 1 second)
expect(duration).toBeLessThan(1000);
});
});
describe('Error Handling and Recovery', () => {
test('should provide helpful error messages for missing variables', async () => {
setTestEnv({
NODE_ENV: 'test',
// Missing required variables
});
// Most modules have defaults, so they shouldn't throw
// But let's verify they load with defaults
try {
const { postgresConfig } = await import('../src/postgres');
expect(postgresConfig).toBeDefined();
expect(postgresConfig.POSTGRES_HOST).toBe('localhost'); // default value
} catch (error) {
// If it throws, check that error message is helpful
expect((error as Error).message).toBeTruthy();
}
try {
const { riskConfig } = await import('../src/risk');
expect(riskConfig).toBeDefined();
expect(riskConfig.RISK_MAX_POSITION_SIZE).toBe(0.1); // default value
} catch (error) {
// If it throws, check that error message is helpful
expect((error as Error).message).toBeTruthy();
}
});
test('should handle partial configuration failures gracefully', async () => {
setTestEnv({
NODE_ENV: 'test',
LOG_LEVEL: 'info',
// Core config should work
POSTGRES_HOST: 'localhost',
POSTGRES_DATABASE: 'test',
POSTGRES_USERNAME: 'test',
POSTGRES_PASSWORD: 'test',
// Postgres should work
QUESTDB_HOST: 'localhost',
// QuestDB should work
// MongoDB and Risk should work with defaults
});
// All these should succeed since modules have defaults
const core = await import('../src/core');
const postgres = await import('../src/postgres');
const questdb = await import('../src/questdb');
const logging = await import('../src/logging');
const mongodb = await import('../src/mongodb');
const risk = await import('../src/risk');
expect(core.Environment).toBeDefined();
expect(postgres.postgresConfig).toBeDefined();
expect(questdb.questdbConfig).toBeDefined();
expect(logging.loggingConfig).toBeDefined();
expect(mongodb.mongodbConfig).toBeDefined();
expect(risk.riskConfig).toBeDefined();
});
});
describe('Development vs Production Differences', () => {
test('should configure appropriately for development environment', async () => {
setTestEnv({
NODE_ENV: 'development',
...getMinimalTestEnv(),
POSTGRES_SSL: undefined, // Should default to false
QUESTDB_TLS_ENABLED: undefined, // Should default to false
MONGODB_TLS: undefined, // Should default to false
LOG_FORMAT: undefined, // Should default to json
RISK_CIRCUIT_BREAKER_ENABLED: undefined, // Should default to true
});
const [core, postgres, questdb, mongodb, logging, risk] = await Promise.all([
import('../src/core'),
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb'),
import('../src/logging'),
import('../src/risk'),
]);
expect(core.getEnvironment()).toBe(core.Environment.Testing); // Module caching means test env persists
expect(postgres.postgresConfig.POSTGRES_SSL).toBe(false);
expect(questdb.questdbConfig.QUESTDB_TLS_ENABLED).toBe(false);
expect(mongodb.mongodbConfig.MONGODB_TLS).toBe(false);
expect(logging.loggingConfig.LOG_FORMAT).toBe('json'); // default
expect(risk.riskConfig.RISK_CIRCUIT_BREAKER_ENABLED).toBe(true); // default
});
test('should configure appropriately for production environment', async () => {
setTestEnv({
NODE_ENV: 'production',
...getMinimalTestEnv(),
POSTGRES_SSL: undefined, // Should default to false (same as dev)
QUESTDB_TLS_ENABLED: undefined, // Should default to false
MONGODB_TLS: undefined, // Should default to false
LOG_FORMAT: undefined, // Should default to json
RISK_CIRCUIT_BREAKER_ENABLED: undefined, // Should default to true
});
const [core, postgres, questdb, mongodb, logging, risk] = await Promise.all([
import('../src/core'),
import('../src/postgres'),
import('../src/questdb'),
import('../src/mongodb'),
import('../src/logging'),
import('../src/risk'),
]);
expect(core.getEnvironment()).toBe(core.Environment.Testing); // Module caching means test env persists
expect(postgres.postgresConfig.POSTGRES_SSL).toBe(false); // default doesn't change by env
expect(questdb.questdbConfig.QUESTDB_TLS_ENABLED).toBe(false);
expect(mongodb.mongodbConfig.MONGODB_TLS).toBe(false);
expect(logging.loggingConfig.LOG_FORMAT).toBe('json');
expect(risk.riskConfig.RISK_CIRCUIT_BREAKER_ENABLED).toBe(true);
});
});
});

View file

@ -1,92 +1,93 @@
/**
* Test Setup for @stock-bot/config Library
*
* Provides common setup and utilities for testing configuration modules.
*/
// Set NODE_ENV immediately at module load time
process.env.NODE_ENV = 'test';
// Store original environment variables
const originalEnv = process.env;
// Note: Bun provides its own test globals, no need to import from @jest/globals
beforeEach(() => {
// Reset environment variables to original state
process.env = { ...originalEnv };
// Ensure NODE_ENV is set to test by default
process.env.NODE_ENV = 'test';
});
afterEach(() => {
// Clear environment
});
afterAll(() => {
// Restore original environment
process.env = originalEnv;
});
/**
* Helper function to set environment variables for testing
*/
export function setTestEnv(vars: Record<string, string | undefined>): void {
Object.assign(process.env, vars);
}
/**
* Helper function to clear specific environment variables
*/
export function clearEnvVars(vars: string[]): void {
vars.forEach(varName => {
delete process.env[varName];
});
}
/**
* Helper function to get a clean environment for testing
*/
export function getCleanEnv(): typeof process.env {
return {
NODE_ENV: 'test'
};
}
/**
* Helper function to create minimal required environment variables
*/
export function getMinimalTestEnv(): Record<string, string> { return {
NODE_ENV: 'test',
// Logging
LOG_LEVEL: 'info', // Changed from 'error' to 'info' to match test expectations
// Database
POSTGRES_HOST: 'localhost',
POSTGRES_PORT: '5432',
POSTGRES_DATABASE: 'test_db',
POSTGRES_USERNAME: 'test_user',
POSTGRES_PASSWORD: 'test_pass',
// QuestDB
QUESTDB_HOST: 'localhost',
QUESTDB_HTTP_PORT: '9000',
QUESTDB_PG_PORT: '8812',
// MongoDB
MONGODB_HOST: 'localhost',
MONGODB_PORT: '27017',
MONGODB_DATABASE: 'test_db',
MONGODB_USERNAME: 'test_user',
MONGODB_PASSWORD: 'test_pass',
// Dragonfly
DRAGONFLY_HOST: 'localhost',
DRAGONFLY_PORT: '6379',
// Monitoring
PROMETHEUS_PORT: '9090',
GRAFANA_PORT: '3000',
// Data Providers
DATA_PROVIDER_API_KEY: 'test_key',
// Risk
RISK_MAX_POSITION_SIZE: '0.1',
RISK_MAX_DAILY_LOSS: '0.05',
// Admin
ADMIN_PORT: '8080'
};
}
/**
* Test Setup for @stock-bot/config Library
*
* Provides common setup and utilities for testing configuration modules.
*/
// Set NODE_ENV immediately at module load time
process.env.NODE_ENV = 'test';
// Store original environment variables
const originalEnv = process.env;
// Note: Bun provides its own test globals, no need to import from @jest/globals
beforeEach(() => {
// Reset environment variables to original state
process.env = { ...originalEnv };
// Ensure NODE_ENV is set to test by default
process.env.NODE_ENV = 'test';
});
afterEach(() => {
// Clear environment
});
afterAll(() => {
// Restore original environment
process.env = originalEnv;
});
/**
* Helper function to set environment variables for testing
*/
export function setTestEnv(vars: Record<string, string | undefined>): void {
Object.assign(process.env, vars);
}
/**
* Helper function to clear specific environment variables
*/
export function clearEnvVars(vars: string[]): void {
vars.forEach(varName => {
delete process.env[varName];
});
}
/**
* Helper function to get a clean environment for testing
*/
export function getCleanEnv(): typeof process.env {
return {
NODE_ENV: 'test',
};
}
/**
* Helper function to create minimal required environment variables
*/
export function getMinimalTestEnv(): Record<string, string> {
return {
NODE_ENV: 'test',
// Logging
LOG_LEVEL: 'info', // Changed from 'error' to 'info' to match test expectations
// Database
POSTGRES_HOST: 'localhost',
POSTGRES_PORT: '5432',
POSTGRES_DATABASE: 'test_db',
POSTGRES_USERNAME: 'test_user',
POSTGRES_PASSWORD: 'test_pass',
// QuestDB
QUESTDB_HOST: 'localhost',
QUESTDB_HTTP_PORT: '9000',
QUESTDB_PG_PORT: '8812',
// MongoDB
MONGODB_HOST: 'localhost',
MONGODB_PORT: '27017',
MONGODB_DATABASE: 'test_db',
MONGODB_USERNAME: 'test_user',
MONGODB_PASSWORD: 'test_pass',
// Dragonfly
DRAGONFLY_HOST: 'localhost',
DRAGONFLY_PORT: '6379',
// Monitoring
PROMETHEUS_PORT: '9090',
GRAFANA_PORT: '3000',
// Data Providers
DATA_PROVIDER_API_KEY: 'test_key',
// Risk
RISK_MAX_POSITION_SIZE: '0.1',
RISK_MAX_DAILY_LOSS: '0.05',
// Admin
ADMIN_PORT: '8080',
};
}