switched to new config and removed old
This commit is contained in:
parent
6b69bcbcaa
commit
269364fbc8
70 changed files with 889 additions and 2978 deletions
|
|
@ -12,11 +12,13 @@
|
|||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stock-bot/config-new": "*",
|
||||
"@stock-bot/cache": "*",
|
||||
"@stock-bot/config": "*",
|
||||
"@stock-bot/logger": "*",
|
||||
"@stock-bot/mongodb-client": "*",
|
||||
"@stock-bot/postgres-client": "*",
|
||||
"@stock-bot/questdb-client": "*",
|
||||
"@stock-bot/queue": "*",
|
||||
"@stock-bot/shutdown": "*",
|
||||
"hono": "^4.0.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,14 +5,13 @@
|
|||
// Framework imports
|
||||
import { Hono } from 'hono';
|
||||
import { cors } from 'hono/cors';
|
||||
|
||||
// Library imports
|
||||
import { initializeServiceConfig } from '@stock-bot/config-new';
|
||||
import { initializeServiceConfig } from '@stock-bot/config';
|
||||
import { getLogger, setLoggerConfig, shutdownLoggers } from '@stock-bot/logger';
|
||||
import { createAndConnectMongoDBClient, MongoDBClient } from '@stock-bot/mongodb-client';
|
||||
import { createAndConnectPostgreSQLClient, PostgreSQLClient } from '@stock-bot/postgres-client';
|
||||
import { getQueue, initializeQueueSystem, shutdownAllQueues } from '@stock-bot/queue';
|
||||
import { Shutdown } from '@stock-bot/shutdown';
|
||||
|
||||
// Local imports
|
||||
import { exchangeRoutes, healthRoutes, queueRoutes } from './routes';
|
||||
|
||||
|
|
@ -20,6 +19,7 @@ import { exchangeRoutes, healthRoutes, queueRoutes } from './routes';
|
|||
const config = await initializeServiceConfig();
|
||||
const serviceConfig = config.service;
|
||||
const databaseConfig = config.database;
|
||||
const queueConfig = config.queue;
|
||||
|
||||
// Initialize logger with config
|
||||
const loggingConfig = config.logging;
|
||||
|
|
@ -50,6 +50,7 @@ const PORT = serviceConfig.port;
|
|||
let server: ReturnType<typeof Bun.serve> | null = null;
|
||||
let postgresClient: PostgreSQLClient | null = null;
|
||||
let mongoClient: MongoDBClient | null = null;
|
||||
// Queue system will be initialized globally
|
||||
|
||||
// Initialize shutdown manager
|
||||
const shutdown = Shutdown.getInstance({ timeout: 15000 });
|
||||
|
|
@ -97,6 +98,16 @@ async function initializeServices() {
|
|||
});
|
||||
logger.info('PostgreSQL connected');
|
||||
|
||||
// Initialize queue system
|
||||
logger.info('Initializing queue system...');
|
||||
await initializeQueueSystem({
|
||||
redis: queueConfig.redis,
|
||||
defaultJobOptions: queueConfig.defaultJobOptions,
|
||||
workers: 5,
|
||||
concurrency: 20,
|
||||
});
|
||||
logger.info('Queue system initialized');
|
||||
|
||||
logger.info('All services initialized successfully');
|
||||
} catch (error) {
|
||||
logger.error('Failed to initialize services', { error });
|
||||
|
|
@ -130,6 +141,16 @@ shutdown.onShutdown(async () => {
|
|||
}
|
||||
});
|
||||
|
||||
shutdown.onShutdown(async () => {
|
||||
logger.info('Shutting down queue system...');
|
||||
try {
|
||||
await shutdownAllQueues();
|
||||
logger.info('Queue system shut down');
|
||||
} catch (error) {
|
||||
logger.error('Error shutting down queue system', { error });
|
||||
}
|
||||
});
|
||||
|
||||
shutdown.onShutdown(async () => {
|
||||
logger.info('Disconnecting from databases...');
|
||||
try {
|
||||
|
|
@ -160,4 +181,7 @@ startServer().catch(error => {
|
|||
process.exit(1);
|
||||
});
|
||||
|
||||
logger.info('Data service startup initiated');
|
||||
logger.info('Data service startup initiated');
|
||||
|
||||
// Export queue functions for providers
|
||||
export { getQueue };
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@
|
|||
],
|
||||
"references": [
|
||||
{ "path": "../../libs/types" },
|
||||
{ "path": "../../libs/config-new" },
|
||||
{ "path": "../../libs/config" },
|
||||
{ "path": "../../libs/logger" },
|
||||
{ "path": "../../libs/cache" },
|
||||
{ "path": "../../libs/queue" },
|
||||
{ "path": "../../libs/mongodb-client" },
|
||||
{ "path": "../../libs/postgres-client" },
|
||||
{ "path": "../../libs/questdb-client" },
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"@stock-bot/config-new#build",
|
||||
"@stock-bot/config#build",
|
||||
"@stock-bot/logger#build",
|
||||
"@stock-bot/cache#build",
|
||||
"@stock-bot/queue#build",
|
||||
"@stock-bot/mongodb-client#build",
|
||||
"@stock-bot/postgres-client#build",
|
||||
"@stock-bot/questdb-client#build",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stock-bot/config-new": "*",
|
||||
"@stock-bot/config": "*",
|
||||
"@stock-bot/logger": "*",
|
||||
"@stock-bot/mongodb-client": "*",
|
||||
"@stock-bot/postgres-client": "*",
|
||||
|
|
|
|||
|
|
@ -5,19 +5,17 @@
|
|||
// Framework imports
|
||||
import { Hono } from 'hono';
|
||||
import { cors } from 'hono/cors';
|
||||
|
||||
// Library imports
|
||||
import { initializeServiceConfig } from '@stock-bot/config-new';
|
||||
import { initializeServiceConfig } from '@stock-bot/config';
|
||||
import { getLogger, setLoggerConfig, shutdownLoggers } from '@stock-bot/logger';
|
||||
import { createAndConnectMongoDBClient, MongoDBClient } from '@stock-bot/mongodb-client';
|
||||
import { createAndConnectPostgreSQLClient, PostgreSQLClient } from '@stock-bot/postgres-client';
|
||||
import { Shutdown } from '@stock-bot/shutdown';
|
||||
|
||||
// Local imports
|
||||
import { enhancedSyncManager } from './services/enhanced-sync-manager';
|
||||
import { syncManager } from './services/sync-manager';
|
||||
// Local imports
|
||||
import { setMongoDBClient, setPostgreSQLClient } from './clients';
|
||||
import { healthRoutes, syncRoutes, enhancedSyncRoutes, statsRoutes } from './routes';
|
||||
import { enhancedSyncRoutes, healthRoutes, statsRoutes, syncRoutes } from './routes';
|
||||
|
||||
// Initialize configuration with automatic monorepo config inheritance
|
||||
const config = await initializeServiceConfig();
|
||||
|
|
|
|||
|
|
@ -1,22 +1,28 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowImportingTsExtensions": false,
|
||||
"noEmit": false,
|
||||
"allowSyntheticDefaultImports": true
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/test/**",
|
||||
"**/tests/**",
|
||||
"**/__tests__/**"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../libs/types" },
|
||||
{ "path": "../../libs/config" },
|
||||
{ "path": "../../libs/logger" },
|
||||
{ "path": "../../libs/cache" },
|
||||
{ "path": "../../libs/queue" },
|
||||
{ "path": "../../libs/mongodb-client" },
|
||||
{ "path": "../../libs/postgres-client" },
|
||||
{ "path": "../../libs/questdb-client" },
|
||||
{ "path": "../../libs/shutdown" }
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stock-bot/config-new": "*",
|
||||
"@stock-bot/config": "*",
|
||||
"@stock-bot/logger": "*",
|
||||
"@stock-bot/mongodb-client": "*",
|
||||
"@stock-bot/postgres-client": "*",
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@
|
|||
*/
|
||||
import { Hono } from 'hono';
|
||||
import { cors } from 'hono/cors';
|
||||
import { initializeServiceConfig } from '@stock-bot/config-new';
|
||||
import { getLogger, shutdownLoggers, setLoggerConfig } from '@stock-bot/logger';
|
||||
import { initializeServiceConfig } from '@stock-bot/config';
|
||||
import { getLogger, setLoggerConfig, shutdownLoggers } from '@stock-bot/logger';
|
||||
import { createAndConnectMongoDBClient, MongoDBClient } from '@stock-bot/mongodb-client';
|
||||
import { createAndConnectPostgreSQLClient, PostgreSQLClient } from '@stock-bot/postgres-client';
|
||||
import { Shutdown } from '@stock-bot/shutdown';
|
||||
import { setPostgreSQLClient, setMongoDBClient } from './clients';
|
||||
// Import routes
|
||||
import { exchangeRoutes } from './routes/exchange.routes';
|
||||
import { healthRoutes } from './routes/health.routes';
|
||||
import { setMongoDBClient, setPostgreSQLClient } from './clients';
|
||||
|
||||
// Initialize configuration with automatic monorepo config inheritance
|
||||
const config = await initializeServiceConfig();
|
||||
|
|
|
|||
119
bun.lock
119
bun.lock
|
|
@ -47,22 +47,16 @@
|
|||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@stock-bot/cache": "*",
|
||||
"@stock-bot/event-bus": "*",
|
||||
"@stock-bot/http": "*",
|
||||
"@stock-bot/config": "*",
|
||||
"@stock-bot/logger": "*",
|
||||
"@stock-bot/mongodb-client": "*",
|
||||
"@stock-bot/postgres-client": "*",
|
||||
"@stock-bot/questdb-client": "*",
|
||||
"@stock-bot/queue": "*",
|
||||
"@stock-bot/shutdown": "*",
|
||||
"@stock-bot/types": "*",
|
||||
"chromium-bidi": "^5.3.1",
|
||||
"electron": "^36.4.0",
|
||||
"hono": "^4.0.0",
|
||||
"p-limit": "^6.2.0",
|
||||
"ws": "^8.0.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ws": "^8.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
},
|
||||
},
|
||||
|
|
@ -70,6 +64,7 @@
|
|||
"name": "@stock-bot/data-sync-service",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@stock-bot/config": "*",
|
||||
"@stock-bot/logger": "*",
|
||||
"@stock-bot/mongodb-client": "*",
|
||||
"@stock-bot/postgres-client": "*",
|
||||
|
|
@ -84,7 +79,7 @@
|
|||
"name": "@stock-bot/web-api",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@stock-bot/config-new": "*",
|
||||
"@stock-bot/config": "*",
|
||||
"@stock-bot/logger": "*",
|
||||
"@stock-bot/mongodb-client": "*",
|
||||
"@stock-bot/postgres-client": "*",
|
||||
|
|
@ -176,8 +171,8 @@
|
|||
"typescript": "^5.3.0",
|
||||
},
|
||||
},
|
||||
"libs/config-new": {
|
||||
"name": "@stock-bot/config-new",
|
||||
"libs/config": {
|
||||
"name": "@stock-bot/config",
|
||||
"version": "1.0.0",
|
||||
"bin": {
|
||||
"config-cli": "./dist/cli.js",
|
||||
|
|
@ -519,8 +514,6 @@
|
|||
|
||||
"@balena/dockerignore": ["@balena/dockerignore@1.0.2", "", {}, "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q=="],
|
||||
|
||||
"@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
|
||||
|
|
@ -829,7 +822,7 @@
|
|||
|
||||
"@stock-bot/config": ["@stock-bot/config@workspace:libs/config"],
|
||||
|
||||
"@stock-bot/config-new": ["@stock-bot/config-new@workspace:libs/config-new"],
|
||||
"@stock-bot/config": ["@stock-bot/config@workspace:libs/config"],
|
||||
|
||||
"@stock-bot/data-frame": ["@stock-bot/data-frame@workspace:libs/data-frame"],
|
||||
|
||||
|
|
@ -895,8 +888,6 @@
|
|||
|
||||
"@types/bun": ["@types/bun@1.2.15", "", { "dependencies": { "bun-types": "1.2.15" } }, "sha512-U1ljPdBEphF0nw1MIk0hI7kPg7dFdPyM7EenHsp6W5loNHl7zqy6JQf/RKCgnUn2KDzUpkBwHPnEJEjII594bA=="],
|
||||
|
||||
"@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="],
|
||||
|
||||
"@types/cookiejar": ["@types/cookiejar@2.1.5", "", {}, "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q=="],
|
||||
|
||||
"@types/docker-modem": ["@types/docker-modem@3.0.6", "", { "dependencies": { "@types/node": "*", "@types/ssh2": "*" } }, "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg=="],
|
||||
|
|
@ -913,8 +904,6 @@
|
|||
|
||||
"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
|
||||
|
||||
"@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="],
|
||||
|
||||
"@types/methods": ["@types/methods@1.1.4", "", {}, "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ=="],
|
||||
|
||||
"@types/mongodb": ["@types/mongodb@4.0.7", "", { "dependencies": { "mongodb": "*" } }, "sha512-lPUYPpzA43baXqnd36cZ9xxorprybxXDzteVKCPAdp14ppHtFJHnXYvNpmBvtMUTb5fKXVv6sVbzo1LHkWhJlw=="],
|
||||
|
|
@ -937,8 +926,6 @@
|
|||
|
||||
"@types/react-window": ["@types/react-window@1.8.8", "", { "dependencies": { "@types/react": "*" } }, "sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q=="],
|
||||
|
||||
"@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="],
|
||||
|
||||
"@types/semver": ["@types/semver@7.7.0", "", {}, "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA=="],
|
||||
|
||||
"@types/ssh2": ["@types/ssh2@0.5.52", "", { "dependencies": { "@types/node": "*", "@types/ssh2-streams": "*" } }, "sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg=="],
|
||||
|
|
@ -955,10 +942,6 @@
|
|||
|
||||
"@types/whatwg-url": ["@types/whatwg-url@11.0.5", "", { "dependencies": { "@types/webidl-conversions": "*" } }, "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ=="],
|
||||
|
||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
||||
|
||||
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
|
||||
|
||||
"@types/yup": ["@types/yup@0.32.0", "", { "dependencies": { "yup": "*" } }, "sha512-Gr2lllWTDxGVYHgWfL8szjdedERpNgm44L9BDL2cmcHG7Bfd6taEpiW3ayMFLaYvlJr/6bFXDJdh6L406AGlFg=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.34.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.34.0", "@typescript-eslint/type-utils": "8.34.0", "@typescript-eslint/utils": "8.34.0", "@typescript-eslint/visitor-keys": "8.34.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.34.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w=="],
|
||||
|
|
@ -1089,8 +1072,6 @@
|
|||
|
||||
"body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
|
||||
|
||||
"boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="],
|
||||
|
||||
"bowser": ["bowser@2.11.0", "", {}, "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="],
|
||||
|
||||
"bplist-parser": ["bplist-parser@0.2.0", "", { "dependencies": { "big-integer": "^1.6.44" } }, "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw=="],
|
||||
|
|
@ -1145,16 +1126,12 @@
|
|||
|
||||
"chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
|
||||
|
||||
"chromium-bidi": ["chromium-bidi@5.3.1", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-fbkgn0/m6RIRknVEez+QOYuvukUomBC0XnS8fgdbl9FeunjR3vUvPN6iYrbzXIuaJXYOwGU8FZgOTDzBImGvLw=="],
|
||||
|
||||
"cli-table": ["cli-table@0.3.11", "", { "dependencies": { "colors": "1.0.3" } }, "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ=="],
|
||||
|
||||
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
||||
|
||||
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
|
||||
|
|
@ -1253,10 +1230,6 @@
|
|||
|
||||
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
|
||||
|
||||
"detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="],
|
||||
|
||||
"devtools-protocol": ["devtools-protocol@0.0.1473885", "", {}, "sha512-J4UUrUc6r5VZXT7xuPEoHpF+vd1MKBtlAUjPnjXZtt1HgG1ccgUzdMc20COqGtWoSKwk/W9DwJ/GUMs1GpnqnA=="],
|
||||
|
||||
"dezalgo": ["dezalgo@1.0.4", "", { "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig=="],
|
||||
|
||||
"didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="],
|
||||
|
|
@ -1283,8 +1256,6 @@
|
|||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"electron": ["electron@36.4.0", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^22.7.7", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-LLOOZEuW5oqvnjC7HBQhIqjIIJAZCIFjQxltQGLfEC7XFsBoZgQ3u3iFj+Kzw68Xj97u1n57Jdt7P98qLvUibQ=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.166", "", {}, "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
|
@ -1295,8 +1266,6 @@
|
|||
|
||||
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||
|
||||
"env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
|
||||
|
||||
"es-abstract": ["es-abstract@1.24.0", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
|
@ -1313,8 +1282,6 @@
|
|||
|
||||
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
|
||||
|
||||
"es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="],
|
||||
|
||||
"esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
|
@ -1383,8 +1350,6 @@
|
|||
|
||||
"express-rate-limit": ["express-rate-limit@7.5.0", "", { "peerDependencies": { "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg=="],
|
||||
|
||||
"extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="],
|
||||
|
||||
"fast-copy": ["fast-copy@3.0.2", "", {}, "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
|
@ -1405,8 +1370,6 @@
|
|||
|
||||
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
||||
|
||||
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
|
||||
|
||||
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
||||
|
|
@ -1447,8 +1410,6 @@
|
|||
|
||||
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
||||
|
||||
"fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
|
||||
|
||||
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
|
||||
|
|
@ -1471,7 +1432,7 @@
|
|||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
|
||||
"get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
|
||||
|
||||
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
|
||||
|
||||
|
|
@ -1483,8 +1444,6 @@
|
|||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="],
|
||||
|
||||
"globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="],
|
||||
|
||||
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
|
||||
|
|
@ -1659,12 +1618,8 @@
|
|||
|
||||
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
||||
|
||||
"json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="],
|
||||
|
||||
"json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
|
||||
|
||||
"jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
|
||||
|
||||
"jsonify": ["jsonify@0.0.1", "", {}, "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg=="],
|
||||
|
||||
"jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="],
|
||||
|
|
@ -1709,8 +1664,6 @@
|
|||
|
||||
"make-dir": ["make-dir@3.1.0", "", { "dependencies": { "semver": "^6.0.0" } }, "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw=="],
|
||||
|
||||
"matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||
|
|
@ -1745,8 +1698,6 @@
|
|||
|
||||
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
|
||||
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
||||
|
||||
"mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
|
||||
|
||||
"mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
|
||||
|
|
@ -1871,7 +1822,7 @@
|
|||
|
||||
"p-cancelable": ["p-cancelable@4.0.1", "", {}, "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg=="],
|
||||
|
||||
"p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="],
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
|
|
@ -2091,8 +2042,6 @@
|
|||
|
||||
"rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
|
||||
|
||||
"roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],
|
||||
|
||||
"rollup": ["rollup@3.29.5", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w=="],
|
||||
|
||||
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
||||
|
|
@ -2121,12 +2070,8 @@
|
|||
|
||||
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="],
|
||||
|
||||
"send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
|
||||
|
||||
"serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="],
|
||||
|
||||
"serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
|
||||
|
||||
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
|
||||
|
|
@ -2225,8 +2170,6 @@
|
|||
|
||||
"sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="],
|
||||
|
||||
"sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="],
|
||||
|
||||
"superagent": ["superagent@8.1.2", "", { "dependencies": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.4", "debug": "^4.3.4", "fast-safe-stringify": "^2.1.1", "form-data": "^4.0.0", "formidable": "^2.1.2", "methods": "^1.1.2", "mime": "2.6.0", "qs": "^6.11.0", "semver": "^7.3.8" } }, "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA=="],
|
||||
|
||||
"supertest": ["supertest@6.3.4", "", { "dependencies": { "methods": "^1.1.2", "superagent": "^8.1.2" } }, "sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw=="],
|
||||
|
|
@ -2319,8 +2262,6 @@
|
|||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
|
||||
|
||||
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||
|
||||
"untildify": ["untildify@4.0.0", "", {}, "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw=="],
|
||||
|
|
@ -2367,8 +2308,6 @@
|
|||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"ws": ["ws@8.18.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="],
|
||||
|
||||
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
|
@ -2383,7 +2322,7 @@
|
|||
|
||||
"yauzl": ["yauzl@3.2.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "pend": "~1.2.0" } }, "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@1.2.1", "", {}, "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg=="],
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"yup": ["yup@1.6.1", "", { "dependencies": { "property-expr": "^2.0.5", "tiny-case": "^1.0.3", "toposort": "^2.0.2", "type-fest": "^2.19.0" } }, "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA=="],
|
||||
|
||||
|
|
@ -2409,10 +2348,6 @@
|
|||
|
||||
"@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
||||
|
||||
"@electron/get/got": ["got@11.8.6", "", { "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" } }, "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g=="],
|
||||
|
||||
"@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||
|
|
@ -2445,7 +2380,7 @@
|
|||
|
||||
"@stock-bot/config/eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="],
|
||||
|
||||
"@stock-bot/config-new/@types/node": ["@types/node@20.19.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q=="],
|
||||
"@stock-bot/config/@types/node": ["@types/node@20.19.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q=="],
|
||||
|
||||
"@stock-bot/data-frame/@types/node": ["@types/node@20.19.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q=="],
|
||||
|
||||
|
|
@ -2517,14 +2452,10 @@
|
|||
|
||||
"body-parser/qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
|
||||
|
||||
"cacheable-request/get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
|
||||
|
||||
"chokidar/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="],
|
||||
|
||||
"compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"decompress-response/mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
|
||||
|
|
@ -2565,8 +2496,6 @@
|
|||
|
||||
"express/qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
|
||||
|
||||
"extract-zip/yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
|
||||
|
||||
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
|
@ -2607,8 +2536,6 @@
|
|||
|
||||
"os-dns-native/node-addon-api": ["node-addon-api@4.3.0", "", {}, "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="],
|
||||
|
||||
"p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"pg-mem/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
|
||||
|
|
@ -2629,8 +2556,6 @@
|
|||
|
||||
"send/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
|
||||
|
||||
"serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
|
||||
|
||||
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
|
||||
|
||||
"tailwindcss/jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="],
|
||||
|
|
@ -2651,22 +2576,6 @@
|
|||
|
||||
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"@electron/get/got/@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="],
|
||||
|
||||
"@electron/get/got/@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="],
|
||||
|
||||
"@electron/get/got/cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="],
|
||||
|
||||
"@electron/get/got/cacheable-request": ["cacheable-request@7.0.4", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg=="],
|
||||
|
||||
"@electron/get/got/http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="],
|
||||
|
||||
"@electron/get/got/lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="],
|
||||
|
||||
"@electron/get/got/p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="],
|
||||
|
||||
"@electron/get/got/responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="],
|
||||
|
||||
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
|
|
@ -2923,8 +2832,6 @@
|
|||
|
||||
"express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||
|
||||
"extract-zip/yauzl/buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
|
||||
|
||||
"glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||
|
||||
"lazystream/readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
|
||||
|
|
@ -2941,8 +2848,6 @@
|
|||
|
||||
"mongodb-memory-server-core/mongodb/mongodb-connection-string-url": ["mongodb-connection-string-url@2.6.0", "", { "dependencies": { "@types/whatwg-url": "^8.2.1", "whatwg-url": "^11.0.0" } }, "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ=="],
|
||||
|
||||
"p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
|
||||
"prebuild-install/tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
|
||||
|
|
@ -2969,8 +2874,6 @@
|
|||
|
||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||
|
||||
"@electron/get/got/cacheable-request/normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="],
|
||||
|
||||
"@mongodb-js/oidc-plugin/express/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
|
||||
|
||||
"@mongodb-js/oidc-plugin/express/body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
||||
|
|
|
|||
|
|
@ -74,6 +74,22 @@
|
|||
"baseUrl": "https://query1.finance.yahoo.com"
|
||||
}
|
||||
},
|
||||
"queue": {
|
||||
"redis": {
|
||||
"host": "localhost",
|
||||
"port": 6379,
|
||||
"db": 1
|
||||
},
|
||||
"defaultJobOptions": {
|
||||
"attempts": 3,
|
||||
"backoff": {
|
||||
"type": "exponential",
|
||||
"delay": 1000
|
||||
},
|
||||
"removeOnComplete": true,
|
||||
"removeOnFail": false
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"realtime": true,
|
||||
"backtesting": true,
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
# Environment
|
||||
NODE_ENV=development
|
||||
|
||||
# Service Configuration
|
||||
STOCKBOT_SERVICE_NAME=stock-bot-service
|
||||
STOCKBOT_SERVICE_PORT=3000
|
||||
|
||||
# Database Configuration
|
||||
STOCKBOT_DATABASE_POSTGRES_HOST=localhost
|
||||
STOCKBOT_DATABASE_POSTGRES_PORT=5432
|
||||
STOCKBOT_DATABASE_POSTGRES_DATABASE=stockbot
|
||||
STOCKBOT_DATABASE_POSTGRES_USER=postgres
|
||||
STOCKBOT_DATABASE_POSTGRES_PASSWORD=postgres
|
||||
|
||||
STOCKBOT_DATABASE_QUESTDB_HOST=localhost
|
||||
STOCKBOT_DATABASE_QUESTDB_ILP_PORT=9009
|
||||
STOCKBOT_DATABASE_QUESTDB_HTTP_PORT=9000
|
||||
|
||||
STOCKBOT_DATABASE_MONGODB_HOST=localhost
|
||||
STOCKBOT_DATABASE_MONGODB_PORT=27017
|
||||
STOCKBOT_DATABASE_MONGODB_DATABASE=stockbot
|
||||
|
||||
STOCKBOT_DATABASE_DRAGONFLY_HOST=localhost
|
||||
STOCKBOT_DATABASE_DRAGONFLY_PORT=6379
|
||||
|
||||
# Provider Configuration
|
||||
STOCKBOT_PROVIDERS_EOD_API_KEY=your_eod_api_key
|
||||
STOCKBOT_PROVIDERS_EOD_ENABLED=true
|
||||
|
||||
STOCKBOT_PROVIDERS_IB_ENABLED=false
|
||||
STOCKBOT_PROVIDERS_IB_GATEWAY_HOST=localhost
|
||||
STOCKBOT_PROVIDERS_IB_GATEWAY_PORT=5000
|
||||
STOCKBOT_PROVIDERS_IB_ACCOUNT=your_account_id
|
||||
|
||||
STOCKBOT_PROVIDERS_QM_ENABLED=false
|
||||
STOCKBOT_PROVIDERS_QM_USERNAME=your_username
|
||||
STOCKBOT_PROVIDERS_QM_PASSWORD=your_password
|
||||
STOCKBOT_PROVIDERS_QM_WEBMASTER_ID=your_webmaster_id
|
||||
|
||||
# Logging
|
||||
STOCKBOT_LOGGING_LEVEL=info
|
||||
STOCKBOT_LOGGING_LOKI_ENABLED=false
|
||||
STOCKBOT_LOGGING_LOKI_HOST=localhost
|
||||
STOCKBOT_LOGGING_LOKI_PORT=3100
|
||||
|
||||
# HTTP Proxy (optional)
|
||||
STOCKBOT_HTTP_PROXY_ENABLED=false
|
||||
STOCKBOT_HTTP_PROXY_URL=http://proxy.example.com:8080
|
||||
STOCKBOT_HTTP_PROXY_AUTH_USERNAME=username
|
||||
STOCKBOT_HTTP_PROXY_AUTH_PASSWORD=password
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
# @stock-bot/config-new
|
||||
|
||||
A robust, type-safe configuration library for the Stock Bot application. Built with Zod for validation and supports multiple configuration sources with proper precedence.
|
||||
|
||||
## Features
|
||||
|
||||
- **Type-safe configuration** with Zod schemas
|
||||
- **Multiple configuration sources**: JSON files and environment variables
|
||||
- **Environment-specific overrides** (development, test, production)
|
||||
- **Dynamic provider configurations**
|
||||
- **No circular dependencies** - designed to be used by all other libraries
|
||||
- **Clear error messages** with validation
|
||||
- **Runtime configuration updates** (useful for testing)
|
||||
- **Singleton pattern** for global configuration access
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
bun add @stock-bot/config-new
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import { initializeConfig, getConfig } from '@stock-bot/config-new';
|
||||
|
||||
// Initialize configuration (call once at app startup)
|
||||
await initializeConfig();
|
||||
|
||||
// Get configuration
|
||||
const config = getConfig();
|
||||
console.log(config.database.postgres.host);
|
||||
|
||||
// Use convenience functions
|
||||
import { getDatabaseConfig, isProduction } from '@stock-bot/config-new';
|
||||
|
||||
const dbConfig = getDatabaseConfig();
|
||||
if (isProduction()) {
|
||||
// Production-specific logic
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
```typescript
|
||||
import { ConfigManager } from '@stock-bot/config-new';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Define your schema
|
||||
const myConfigSchema = z.object({
|
||||
app: z.object({
|
||||
name: z.string(),
|
||||
version: z.string(),
|
||||
}),
|
||||
features: z.object({
|
||||
enableBeta: z.boolean().default(false),
|
||||
}),
|
||||
});
|
||||
|
||||
// Create config manager
|
||||
const configManager = new ConfigManager({
|
||||
configPath: './my-config',
|
||||
});
|
||||
|
||||
// Initialize with schema
|
||||
const config = await configManager.initialize(myConfigSchema);
|
||||
```
|
||||
|
||||
### Provider-Specific Configuration
|
||||
|
||||
```typescript
|
||||
import { getProviderConfig } from '@stock-bot/config-new';
|
||||
|
||||
// Get provider configuration
|
||||
const eodConfig = getProviderConfig('eod');
|
||||
console.log(eodConfig.apiKey);
|
||||
|
||||
// Check if provider is enabled
|
||||
if (eodConfig.enabled) {
|
||||
// Use EOD provider
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Environment variables are loaded with the `STOCKBOT_` prefix and follow a naming convention:
|
||||
|
||||
```bash
|
||||
# Database configuration
|
||||
STOCKBOT_DATABASE_POSTGRES_HOST=localhost
|
||||
STOCKBOT_DATABASE_POSTGRES_PORT=5432
|
||||
|
||||
# Provider configuration
|
||||
STOCKBOT_PROVIDERS_EOD_API_KEY=your_api_key
|
||||
STOCKBOT_PROVIDERS_EOD_ENABLED=true
|
||||
|
||||
# Service configuration
|
||||
STOCKBOT_SERVICE_PORT=3000
|
||||
STOCKBOT_LOGGING_LEVEL=debug
|
||||
```
|
||||
|
||||
### Configuration Precedence
|
||||
|
||||
Configuration is loaded in the following order (later sources override earlier ones):
|
||||
|
||||
1. `config/default.json` - Base configuration
|
||||
2. `config/{environment}.json` - Environment-specific overrides
|
||||
3. Environment variables - Highest priority
|
||||
|
||||
### Advanced Usage
|
||||
|
||||
```typescript
|
||||
import { getConfigManager } from '@stock-bot/config-new';
|
||||
|
||||
const manager = getConfigManager();
|
||||
|
||||
// Get specific value by path
|
||||
const port = manager.getValue<number>('service.port');
|
||||
|
||||
// Check if configuration exists
|
||||
if (manager.has('providers.ib')) {
|
||||
// IB provider is configured
|
||||
}
|
||||
|
||||
// Update configuration at runtime (useful for testing)
|
||||
manager.set({
|
||||
logging: {
|
||||
level: 'debug'
|
||||
}
|
||||
});
|
||||
|
||||
// Create typed getter
|
||||
const getQueueConfig = manager.createTypedGetter(queueConfigSchema);
|
||||
const queueConfig = getQueueConfig();
|
||||
```
|
||||
|
||||
## Configuration Schema
|
||||
|
||||
The library provides pre-defined schemas for common configurations:
|
||||
|
||||
### Base Configuration
|
||||
- `environment` - Current environment (development/test/production)
|
||||
- `name` - Application name
|
||||
- `version` - Application version
|
||||
- `debug` - Debug mode flag
|
||||
|
||||
### Service Configuration
|
||||
- `name` - Service name
|
||||
- `port` - Service port
|
||||
- `host` - Service host
|
||||
- `healthCheckPath` - Health check endpoint
|
||||
- `cors` - CORS configuration
|
||||
|
||||
### Database Configuration
|
||||
- `postgres` - PostgreSQL configuration
|
||||
- `questdb` - QuestDB configuration
|
||||
- `mongodb` - MongoDB configuration
|
||||
- `dragonfly` - Dragonfly/Redis configuration
|
||||
|
||||
### Provider Configuration
|
||||
- `eod` - EOD Historical Data provider
|
||||
- `ib` - Interactive Brokers provider
|
||||
- `qm` - QuoteMedia provider
|
||||
- `yahoo` - Yahoo Finance provider
|
||||
|
||||
## Testing
|
||||
|
||||
```typescript
|
||||
import { resetConfig, initializeConfig } from '@stock-bot/config-new';
|
||||
|
||||
beforeEach(() => {
|
||||
resetConfig();
|
||||
});
|
||||
|
||||
test('custom config', async () => {
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.STOCKBOT_SERVICE_PORT = '4000';
|
||||
|
||||
await initializeConfig();
|
||||
const config = getConfig();
|
||||
|
||||
expect(config.service.port).toBe(4000);
|
||||
});
|
||||
```
|
||||
|
||||
## Custom Loaders
|
||||
|
||||
You can create custom configuration loaders:
|
||||
|
||||
```typescript
|
||||
import { ConfigLoader } from '@stock-bot/config-new';
|
||||
|
||||
class ApiConfigLoader implements ConfigLoader {
|
||||
readonly priority = 75; // Between file (50) and env (100)
|
||||
|
||||
async load(): Promise<Record<string, unknown>> {
|
||||
// Fetch configuration from API
|
||||
const response = await fetch('https://api.example.com/config');
|
||||
return response.json();
|
||||
}
|
||||
}
|
||||
|
||||
// Use custom loader
|
||||
const configManager = new ConfigManager({
|
||||
loaders: [
|
||||
new FileLoader('./config', 'production'),
|
||||
new ApiConfigLoader(),
|
||||
new EnvLoader('STOCKBOT_'),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The library provides specific error types:
|
||||
|
||||
```typescript
|
||||
import { ConfigError, ConfigValidationError } from '@stock-bot/config-new';
|
||||
|
||||
try {
|
||||
await initializeConfig();
|
||||
} catch (error) {
|
||||
if (error instanceof ConfigValidationError) {
|
||||
console.error('Validation failed:', error.errors);
|
||||
} else if (error instanceof ConfigError) {
|
||||
console.error('Configuration error:', error.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Initialize once**: Call `initializeConfig()` once at application startup
|
||||
2. **Use schemas**: Always define and validate configurations with Zod schemas
|
||||
3. **Environment variables**: Use the `STOCKBOT_` prefix for all env vars
|
||||
4. **Type safety**: Leverage TypeScript types from the schemas
|
||||
5. **Testing**: Reset configuration between tests with `resetConfig()`
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
{
|
||||
"name": "@stock-bot/config-new",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"test": "bun test",
|
||||
"clean": "rm -rf dist",
|
||||
"cli": "bun run src/cli.ts",
|
||||
"validate": "bun run src/cli.ts --validate",
|
||||
"check": "bun run src/cli.ts --check"
|
||||
},
|
||||
"bin": {
|
||||
"config-cli": "./dist/cli.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.0.0",
|
||||
"@types/node": "^20.10.5",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bun": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
// Export all schemas
|
||||
export * from './schemas';
|
||||
|
||||
// Export types
|
||||
export * from './types';
|
||||
|
||||
// Export errors
|
||||
export * from './errors';
|
||||
|
||||
// Export loaders
|
||||
export { EnvLoader } from './loaders/env.loader';
|
||||
export { FileLoader } from './loaders/file.loader';
|
||||
|
||||
// Export ConfigManager
|
||||
export { ConfigManager } from './config-manager';
|
||||
|
||||
// Export utilities
|
||||
export * from './utils/secrets';
|
||||
export * from './utils/validation';
|
||||
|
||||
// Import necessary types for singleton
|
||||
import { ConfigManager } from './config-manager';
|
||||
import { AppConfig, appConfigSchema } from './schemas';
|
||||
import { FileLoader } from './loaders/file.loader';
|
||||
import { EnvLoader } from './loaders/env.loader';
|
||||
|
||||
// Create singleton instance
|
||||
let configInstance: ConfigManager<AppConfig> | null = null;
|
||||
|
||||
/**
|
||||
* Initialize the global configuration
|
||||
*/
|
||||
export async function initializeConfig(
|
||||
configPath?: string
|
||||
): Promise<AppConfig> {
|
||||
if (!configInstance) {
|
||||
configInstance = new ConfigManager<AppConfig>({
|
||||
configPath,
|
||||
});
|
||||
}
|
||||
return configInstance.initialize(appConfigSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize configuration for a service in a monorepo
|
||||
* Automatically loads configs from:
|
||||
* 1. Root config directory (../../config)
|
||||
* 2. Service-specific config directory (./config)
|
||||
* 3. Environment variables
|
||||
*/
|
||||
export async function initializeServiceConfig(): Promise<AppConfig> {
|
||||
if (!configInstance) {
|
||||
const environment = process.env.NODE_ENV || 'development';
|
||||
configInstance = new ConfigManager<AppConfig>({
|
||||
loaders: [
|
||||
new FileLoader('../../config', environment), // Root config
|
||||
new FileLoader('./config', environment), // Service config
|
||||
new EnvLoader(''), // Environment variables
|
||||
]
|
||||
});
|
||||
}
|
||||
return configInstance.initialize(appConfigSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current configuration
|
||||
*/
|
||||
export function getConfig(): AppConfig {
|
||||
if (!configInstance) {
|
||||
throw new Error('Configuration not initialized. Call initializeConfig() first.');
|
||||
}
|
||||
return configInstance.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration manager instance
|
||||
*/
|
||||
export function getConfigManager(): ConfigManager<AppConfig> {
|
||||
if (!configInstance) {
|
||||
throw new Error('Configuration not initialized. Call initializeConfig() first.');
|
||||
}
|
||||
return configInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset configuration (useful for testing)
|
||||
*/
|
||||
export function resetConfig(): void {
|
||||
if (configInstance) {
|
||||
configInstance.reset();
|
||||
configInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Export convenience functions for common configs
|
||||
export function getDatabaseConfig() {
|
||||
return getConfig().database;
|
||||
}
|
||||
|
||||
export function getServiceConfig() {
|
||||
return getConfig().service;
|
||||
}
|
||||
|
||||
export function getLoggingConfig() {
|
||||
return getConfig().logging;
|
||||
}
|
||||
|
||||
export function getProviderConfig(provider: string) {
|
||||
const providers = getConfig().providers;
|
||||
if (!providers || !(provider in providers)) {
|
||||
throw new Error(`Provider configuration not found: ${provider}`);
|
||||
}
|
||||
return (providers as any)[provider];
|
||||
}
|
||||
|
||||
// Export environment helpers
|
||||
export function isDevelopment(): boolean {
|
||||
return getConfig().environment === 'development';
|
||||
}
|
||||
|
||||
export function isProduction(): boolean {
|
||||
return getConfig().environment === 'production';
|
||||
}
|
||||
|
||||
export function isTest(): boolean {
|
||||
return getConfig().environment === 'test';
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022"],
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["bun-types"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declarationDir": "./dist",
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./tsconfig.tsbuildinfo"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "test"]
|
||||
}
|
||||
|
|
@ -1,53 +1,50 @@
|
|||
# Base environment variables for Stock Bot
|
||||
|
||||
# Environment
|
||||
NODE_ENV=development
|
||||
|
||||
# Service Configuration
|
||||
STOCKBOT_SERVICE_NAME=stock-bot-service
|
||||
STOCKBOT_SERVICE_PORT=3000
|
||||
|
||||
# Database Configuration
|
||||
STOCKBOT_DATABASE_POSTGRES_HOST=localhost
|
||||
STOCKBOT_DATABASE_POSTGRES_PORT=5432
|
||||
STOCKBOT_DATABASE_POSTGRES_DATABASE=stockbot
|
||||
STOCKBOT_DATABASE_POSTGRES_USER=postgres
|
||||
STOCKBOT_DATABASE_POSTGRES_PASSWORD=postgres
|
||||
|
||||
STOCKBOT_DATABASE_QUESTDB_HOST=localhost
|
||||
STOCKBOT_DATABASE_QUESTDB_ILP_PORT=9009
|
||||
STOCKBOT_DATABASE_QUESTDB_HTTP_PORT=9000
|
||||
|
||||
STOCKBOT_DATABASE_MONGODB_HOST=localhost
|
||||
STOCKBOT_DATABASE_MONGODB_PORT=27017
|
||||
STOCKBOT_DATABASE_MONGODB_DATABASE=stockbot
|
||||
|
||||
STOCKBOT_DATABASE_DRAGONFLY_HOST=localhost
|
||||
STOCKBOT_DATABASE_DRAGONFLY_PORT=6379
|
||||
|
||||
# Provider Configuration
|
||||
STOCKBOT_PROVIDERS_EOD_API_KEY=your_eod_api_key
|
||||
STOCKBOT_PROVIDERS_EOD_ENABLED=true
|
||||
|
||||
STOCKBOT_PROVIDERS_IB_ENABLED=false
|
||||
STOCKBOT_PROVIDERS_IB_GATEWAY_HOST=localhost
|
||||
STOCKBOT_PROVIDERS_IB_GATEWAY_PORT=5000
|
||||
STOCKBOT_PROVIDERS_IB_ACCOUNT=your_account_id
|
||||
|
||||
STOCKBOT_PROVIDERS_QM_ENABLED=false
|
||||
STOCKBOT_PROVIDERS_QM_USERNAME=your_username
|
||||
STOCKBOT_PROVIDERS_QM_PASSWORD=your_password
|
||||
STOCKBOT_PROVIDERS_QM_WEBMASTER_ID=your_webmaster_id
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=debug
|
||||
STOCKBOT_LOGGING_LEVEL=info
|
||||
STOCKBOT_LOGGING_LOKI_ENABLED=false
|
||||
STOCKBOT_LOGGING_LOKI_HOST=localhost
|
||||
STOCKBOT_LOGGING_LOKI_PORT=3100
|
||||
|
||||
# Database configuration
|
||||
DRAGONFLY_HOST=localhost
|
||||
DRAGONFLY_PORT=6379
|
||||
DRAGONFLY_PASSWORD=
|
||||
DRAGONFLY_MAX_RETRIES_PER_REQUEST=3
|
||||
|
||||
TIMESCALE_HOST=localhost
|
||||
TIMESCALE_PORT=5432
|
||||
TIMESCALE_DB=stockbot
|
||||
TIMESCALE_USER=postgres
|
||||
TIMESCALE_PASSWORD=postgres
|
||||
|
||||
# Data providers
|
||||
DEFAULT_DATA_PROVIDER=alpaca
|
||||
ALPACA_API_KEY=your_alpaca_key_here
|
||||
ALPACA_API_SECRET=your_alpaca_secret_here
|
||||
POLYGON_API_KEY=your_polygon_key_here
|
||||
|
||||
# Risk parameters
|
||||
RISK_MAX_DRAWDOWN=0.05
|
||||
RISK_MAX_POSITION_SIZE=0.1
|
||||
RISK_MAX_LEVERAGE=1.5
|
||||
RISK_STOP_LOSS_DEFAULT=0.02
|
||||
RISK_TAKE_PROFIT_DEFAULT=0.05
|
||||
|
||||
# Market Data Gateway
|
||||
SERVICE_PORT=4000
|
||||
WEBSOCKET_ENABLED=true
|
||||
WEBSOCKET_PATH=/ws/market-data
|
||||
WEBSOCKET_HEARTBEAT_INTERVAL=30000
|
||||
THROTTLING_MAX_REQUESTS=300
|
||||
THROTTLING_MAX_CONNECTIONS=5
|
||||
CACHING_ENABLED=true
|
||||
CACHING_TTL_SECONDS=60
|
||||
|
||||
# Risk Guardian
|
||||
RISK_CHECKS_PRE_TRADE=true
|
||||
RISK_CHECKS_PORTFOLIO=true
|
||||
RISK_CHECKS_LEVERAGE=true
|
||||
RISK_CHECKS_CONCENTRATION=true
|
||||
ALERTING_ENABLED=true
|
||||
ALERTING_CRITICAL_THRESHOLD=0.8
|
||||
ALERTING_WARNING_THRESHOLD=0.6
|
||||
WATCHDOG_ENABLED=true
|
||||
WATCHDOG_CHECK_INTERVAL=60
|
||||
# HTTP Proxy (optional)
|
||||
STOCKBOT_HTTP_PROXY_ENABLED=false
|
||||
STOCKBOT_HTTP_PROXY_URL=http://proxy.example.com:8080
|
||||
STOCKBOT_HTTP_PROXY_AUTH_USERNAME=username
|
||||
STOCKBOT_HTTP_PROXY_AUTH_PASSWORD=password
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
# Production environment variables for Stock Bot
|
||||
|
||||
# Environment
|
||||
NODE_ENV=production
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=info
|
||||
|
||||
# Database configuration (use environment-specific values)
|
||||
DRAGONFLY_HOST=dragonfly.production
|
||||
DRAGONFLY_PORT=6379
|
||||
DRAGONFLY_MAX_RETRIES_PER_REQUEST=5
|
||||
|
||||
TIMESCALE_HOST=timescale.production
|
||||
TIMESCALE_PORT=5432
|
||||
TIMESCALE_DB=stockbot_prod
|
||||
|
||||
# Risk parameters (more conservative for production)
|
||||
RISK_MAX_DRAWDOWN=0.03
|
||||
RISK_MAX_POSITION_SIZE=0.05
|
||||
RISK_MAX_LEVERAGE=1.0
|
||||
|
||||
# Service settings
|
||||
WEBSOCKET_HEARTBEAT_INTERVAL=15000
|
||||
THROTTLING_MAX_REQUESTS=500
|
||||
THROTTLING_MAX_CONNECTIONS=20
|
||||
CACHING_ENABLED=true
|
||||
CACHING_TTL_SECONDS=30
|
||||
|
|
@ -1,103 +1,243 @@
|
|||
# @stock-bot/config
|
||||
|
||||
A configuration management library for the Stock Bot trading platform.
|
||||
|
||||
## Overview
|
||||
|
||||
This library provides a centralized way to manage configurations across all Stock Bot microservices and components. It includes:
|
||||
|
||||
- Environment-based configuration loading
|
||||
- Strong TypeScript typing and validation using Zod
|
||||
- Default configurations for services
|
||||
- Environment variable parsing helpers
|
||||
- Service-specific configuration modules
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import { databaseConfig, dataProviderConfigs, riskConfig } from '@stock-bot/config';
|
||||
|
||||
// Access database configuration
|
||||
const dragonflyHost = databaseConfig.dragonfly.host;
|
||||
|
||||
// Access data provider configuration
|
||||
const alpacaApiKey = dataProviderConfigs.providers.find(p => p.name === 'alpaca')?.apiKey;
|
||||
|
||||
// Access risk configuration
|
||||
const maxPositionSize = riskConfig.maxPositionSize;
|
||||
```
|
||||
|
||||
### Service-Specific Configuration
|
||||
|
||||
```typescript
|
||||
import { marketDataGatewayConfig, riskGuardianConfig } from '@stock-bot/config';
|
||||
|
||||
// Access Market Data Gateway configuration
|
||||
const websocketPath = marketDataGatewayConfig.websocket.path;
|
||||
|
||||
// Access Risk Guardian configuration
|
||||
const preTradeValidation = riskGuardianConfig.riskChecks.preTradeValidation;
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
The library automatically loads environment variables from `.env` files. You can create environment-specific files:
|
||||
|
||||
- `.env` - Base environment variables
|
||||
- `.env.development` - Development-specific variables
|
||||
- `.env.production` - Production-specific variables
|
||||
- `.env.local` - Local overrides (not to be committed to git)
|
||||
|
||||
## Configuration Modules
|
||||
|
||||
### Core Configuration
|
||||
|
||||
- `Environment` - Enum for different environments
|
||||
- `loadEnvVariables()` - Load environment variables from .env files
|
||||
- `getEnvironment()` - Get the current environment
|
||||
- `validateConfig()` - Validate configuration with Zod schema
|
||||
|
||||
### Database Configuration
|
||||
|
||||
- `databaseConfig` - Database connection settings (Dragonfly, QuestDB, MongoDB, PostgreSQL)
|
||||
|
||||
### Data Provider Configuration
|
||||
|
||||
- `dataProviderConfigs` - Settings for market data providers
|
||||
|
||||
### Risk Configuration
|
||||
|
||||
- `riskConfig` - Risk management parameters (max drawdown, position size, etc.)
|
||||
|
||||
### Service-Specific Configuration
|
||||
|
||||
- `marketDataGatewayConfig` - Configs for the Market Data Gateway service
|
||||
- `riskGuardianConfig` - Configs for the Risk Guardian service
|
||||
|
||||
## Extending
|
||||
|
||||
To add a new service configuration:
|
||||
|
||||
1. Create a new file in `src/services/`
|
||||
2. Define a Zod schema for validation
|
||||
3. Create loading and default configuration functions
|
||||
4. Export from `src/services/index.ts`
|
||||
5. The new configuration will be automatically available from the main package
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
bun install
|
||||
|
||||
# Run tests
|
||||
bun test
|
||||
|
||||
# Type check
|
||||
bun run type-check
|
||||
|
||||
# Lint
|
||||
bun run lint
|
||||
```
|
||||
# @stock-bot/config
|
||||
|
||||
A robust, type-safe configuration library for the Stock Bot application. Built with Zod for validation and supports multiple configuration sources with proper precedence.
|
||||
|
||||
## Features
|
||||
|
||||
- **Type-safe configuration** with Zod schemas
|
||||
- **Multiple configuration sources**: JSON files and environment variables
|
||||
- **Environment-specific overrides** (development, test, production)
|
||||
- **Dynamic provider configurations**
|
||||
- **No circular dependencies** - designed to be used by all other libraries
|
||||
- **Clear error messages** with validation
|
||||
- **Runtime configuration updates** (useful for testing)
|
||||
- **Singleton pattern** for global configuration access
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
bun add @stock-bot/config
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import { initializeConfig, getConfig } from '@stock-bot/config';
|
||||
|
||||
// Initialize configuration (call once at app startup)
|
||||
await initializeConfig();
|
||||
|
||||
// Get configuration
|
||||
const config = getConfig();
|
||||
console.log(config.database.postgres.host);
|
||||
|
||||
// Use convenience functions
|
||||
import { getDatabaseConfig, isProduction } from '@stock-bot/config';
|
||||
|
||||
const dbConfig = getDatabaseConfig();
|
||||
if (isProduction()) {
|
||||
// Production-specific logic
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
```typescript
|
||||
import { ConfigManager } from '@stock-bot/config';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Define your schema
|
||||
const myConfigSchema = z.object({
|
||||
app: z.object({
|
||||
name: z.string(),
|
||||
version: z.string(),
|
||||
}),
|
||||
features: z.object({
|
||||
enableBeta: z.boolean().default(false),
|
||||
}),
|
||||
});
|
||||
|
||||
// Create config manager
|
||||
const configManager = new ConfigManager({
|
||||
configPath: './my-config',
|
||||
});
|
||||
|
||||
// Initialize with schema
|
||||
const config = await configManager.initialize(myConfigSchema);
|
||||
```
|
||||
|
||||
### Provider-Specific Configuration
|
||||
|
||||
```typescript
|
||||
import { getProviderConfig } from '@stock-bot/config';
|
||||
|
||||
// Get provider configuration
|
||||
const eodConfig = getProviderConfig('eod');
|
||||
console.log(eodConfig.apiKey);
|
||||
|
||||
// Check if provider is enabled
|
||||
if (eodConfig.enabled) {
|
||||
// Use EOD provider
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Environment variables are loaded with the `STOCKBOT_` prefix and follow a naming convention:
|
||||
|
||||
```bash
|
||||
# Database configuration
|
||||
STOCKBOT_DATABASE_POSTGRES_HOST=localhost
|
||||
STOCKBOT_DATABASE_POSTGRES_PORT=5432
|
||||
|
||||
# Provider configuration
|
||||
STOCKBOT_PROVIDERS_EOD_API_KEY=your_api_key
|
||||
STOCKBOT_PROVIDERS_EOD_ENABLED=true
|
||||
|
||||
# Service configuration
|
||||
STOCKBOT_SERVICE_PORT=3000
|
||||
STOCKBOT_LOGGING_LEVEL=debug
|
||||
```
|
||||
|
||||
### Configuration Precedence
|
||||
|
||||
Configuration is loaded in the following order (later sources override earlier ones):
|
||||
|
||||
1. `config/default.json` - Base configuration
|
||||
2. `config/{environment}.json` - Environment-specific overrides
|
||||
3. Environment variables - Highest priority
|
||||
|
||||
### Advanced Usage
|
||||
|
||||
```typescript
|
||||
import { getConfigManager } from '@stock-bot/config';
|
||||
|
||||
const manager = getConfigManager();
|
||||
|
||||
// Get specific value by path
|
||||
const port = manager.getValue<number>('service.port');
|
||||
|
||||
// Check if configuration exists
|
||||
if (manager.has('providers.ib')) {
|
||||
// IB provider is configured
|
||||
}
|
||||
|
||||
// Update configuration at runtime (useful for testing)
|
||||
manager.set({
|
||||
logging: {
|
||||
level: 'debug'
|
||||
}
|
||||
});
|
||||
|
||||
// Create typed getter
|
||||
const getQueueConfig = manager.createTypedGetter(queueConfigSchema);
|
||||
const queueConfig = getQueueConfig();
|
||||
```
|
||||
|
||||
## Configuration Schema
|
||||
|
||||
The library provides pre-defined schemas for common configurations:
|
||||
|
||||
### Base Configuration
|
||||
- `environment` - Current environment (development/test/production)
|
||||
- `name` - Application name
|
||||
- `version` - Application version
|
||||
- `debug` - Debug mode flag
|
||||
|
||||
### Service Configuration
|
||||
- `name` - Service name
|
||||
- `port` - Service port
|
||||
- `host` - Service host
|
||||
- `healthCheckPath` - Health check endpoint
|
||||
- `cors` - CORS configuration
|
||||
|
||||
### Database Configuration
|
||||
- `postgres` - PostgreSQL configuration
|
||||
- `questdb` - QuestDB configuration
|
||||
- `mongodb` - MongoDB configuration
|
||||
- `dragonfly` - Dragonfly/Redis configuration
|
||||
|
||||
### Provider Configuration
|
||||
- `eod` - EOD Historical Data provider
|
||||
- `ib` - Interactive Brokers provider
|
||||
- `qm` - QuoteMedia provider
|
||||
- `yahoo` - Yahoo Finance provider
|
||||
|
||||
## Testing
|
||||
|
||||
```typescript
|
||||
import { resetConfig, initializeConfig } from '@stock-bot/config';
|
||||
|
||||
beforeEach(() => {
|
||||
resetConfig();
|
||||
});
|
||||
|
||||
test('custom config', async () => {
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.STOCKBOT_SERVICE_PORT = '4000';
|
||||
|
||||
await initializeConfig();
|
||||
const config = getConfig();
|
||||
|
||||
expect(config.service.port).toBe(4000);
|
||||
});
|
||||
```
|
||||
|
||||
## Custom Loaders
|
||||
|
||||
You can create custom configuration loaders:
|
||||
|
||||
```typescript
|
||||
import { ConfigLoader } from '@stock-bot/config';
|
||||
|
||||
class ApiConfigLoader implements ConfigLoader {
|
||||
readonly priority = 75; // Between file (50) and env (100)
|
||||
|
||||
async load(): Promise<Record<string, unknown>> {
|
||||
// Fetch configuration from API
|
||||
const response = await fetch('https://api.example.com/config');
|
||||
return response.json();
|
||||
}
|
||||
}
|
||||
|
||||
// Use custom loader
|
||||
const configManager = new ConfigManager({
|
||||
loaders: [
|
||||
new FileLoader('./config', 'production'),
|
||||
new ApiConfigLoader(),
|
||||
new EnvLoader('STOCKBOT_'),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The library provides specific error types:
|
||||
|
||||
```typescript
|
||||
import { ConfigError, ConfigValidationError } from '@stock-bot/config';
|
||||
|
||||
try {
|
||||
await initializeConfig();
|
||||
} catch (error) {
|
||||
if (error instanceof ConfigValidationError) {
|
||||
console.error('Validation failed:', error.errors);
|
||||
} else if (error instanceof ConfigError) {
|
||||
console.error('Configuration error:', error.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Initialize once**: Call `initializeConfig()` once at application startup
|
||||
2. **Use schemas**: Always define and validate configurations with Zod schemas
|
||||
3. **Environment variables**: Use the `STOCKBOT_` prefix for all env vars
|
||||
4. **Type safety**: Leverage TypeScript types from the schemas
|
||||
5. **Testing**: Reset configuration between tests with `resetConfig()`
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
# Stock Bot Configuration Library Usage Guide
|
||||
|
||||
This guide shows how to use the Zod-based configuration system in the Stock Bot platform.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { databaseConfig, loggingConfig, riskConfig, dataProvidersConfig } from '@stock-bot/config';
|
||||
|
||||
// Access individual values
|
||||
console.log(`Database: ${databaseConfig.POSTGRES_HOST}:${databaseConfig.POSTGRES_PORT}`);
|
||||
console.log(`Log level: ${loggingConfig.LOG_LEVEL}`);
|
||||
console.log(`Max position size: ${riskConfig.RISK_MAX_POSITION_SIZE}`);
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
All configuration is driven by environment variables. You can set them in:
|
||||
- `.env` files
|
||||
- System environment variables
|
||||
- Docker environment variables
|
||||
|
||||
### Database Configuration
|
||||
```bash
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_NAME=stockbot
|
||||
DB_USER=stockbot
|
||||
DB_PASSWORD=your_password
|
||||
DB_SSL=false
|
||||
DB_POOL_MAX=10
|
||||
```
|
||||
|
||||
### Logging Configuration
|
||||
```bash
|
||||
LOG_LEVEL=info
|
||||
LOG_CONSOLE=true
|
||||
LOKI_HOST=localhost
|
||||
LOKI_PORT=3100
|
||||
LOKI_LABELS=service=market-data-gateway,version=1.0.0
|
||||
```
|
||||
|
||||
### Risk Management Configuration
|
||||
```bash
|
||||
RISK_MAX_POSITION_SIZE=0.1
|
||||
RISK_DEFAULT_STOP_LOSS=0.05
|
||||
RISK_DEFAULT_TAKE_PROFIT=0.15
|
||||
RISK_CIRCUIT_BREAKER_ENABLED=true
|
||||
```
|
||||
|
||||
### Data Provider Configuration
|
||||
```bash
|
||||
DEFAULT_DATA_PROVIDER=alpaca
|
||||
ALPACA_API_KEY=your_api_key
|
||||
ALPACA_API_SECRET=your_api_secret
|
||||
ALPACA_ENABLED=true
|
||||
POLYGON_ENABLED=false
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Type Safety
|
||||
All configurations are fully typed:
|
||||
|
||||
```typescript
|
||||
import type { DatabaseConfig, LoggingConfig, RiskConfig } from '@stock-bot/config';
|
||||
|
||||
function setupDatabase(config: DatabaseConfig) {
|
||||
// TypeScript knows all the available properties
|
||||
return {
|
||||
host: config.POSTGRES_HOST,
|
||||
port: config.POSTGRES_PORT, // number
|
||||
ssl: config.POSTGRES_SSL, // boolean
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Detection
|
||||
```typescript
|
||||
import { getEnvironment, Environment } from '@stock-bot/config';
|
||||
|
||||
const env = getEnvironment();
|
||||
if (env === Environment.Production) {
|
||||
// Production-specific logic
|
||||
}
|
||||
```
|
||||
|
||||
### Data Provider Helpers
|
||||
```typescript
|
||||
import { getProviderConfig, getEnabledProviders, getDefaultProvider } from '@stock-bot/config';
|
||||
|
||||
// Get specific provider
|
||||
const alpaca = getProviderConfig('alpaca');
|
||||
|
||||
// Get all enabled providers
|
||||
const providers = getEnabledProviders();
|
||||
|
||||
// Get default provider
|
||||
const defaultProvider = getDefaultProvider();
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
The library consists of these modules:
|
||||
|
||||
- **core.ts** - Core utilities and environment detection
|
||||
- **database.ts** - Database connection settings
|
||||
- **logging.ts** - Logging and Loki configuration
|
||||
- **risk.ts** - Risk management parameters
|
||||
- **data-providers.ts** - Data provider settings
|
||||
|
||||
## Benefits of This Approach
|
||||
|
||||
1. **Zero Configuration Schema** - No complex schema definitions needed
|
||||
2. **Automatic Type Inference** - TypeScript types are generated automatically
|
||||
3. **Environment Variable Validation** - Invalid values are caught at startup
|
||||
4. **Great Developer Experience** - IntelliSense works perfectly
|
||||
5. **Production Ready** - Used by many large-scale applications
|
||||
|
||||
## Migration from Previous System
|
||||
|
||||
If you're migrating from the old Valibot-based system:
|
||||
|
||||
```typescript
|
||||
// Old way
|
||||
const config = createConfigLoader('database', databaseSchema, defaultConfig)();
|
||||
|
||||
// New way
|
||||
import { databaseConfig } from '@stock-bot/config';
|
||||
// That's it! No schema needed, no validation needed, no complex setup.
|
||||
```
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
[test]
|
||||
# Configure path mapping for tests
|
||||
preload = ["./test/setup.ts"]
|
||||
|
||||
# Test configuration
|
||||
timeout = 5000
|
||||
|
||||
# Set test environment
|
||||
env = { NODE_ENV = "test" }
|
||||
|
||||
[bun]
|
||||
# Enable TypeScript paths resolution
|
||||
paths = {
|
||||
"@/*" = ["./src/*"]
|
||||
}
|
||||
|
|
@ -1,44 +1,36 @@
|
|||
{
|
||||
"name": "@stock-bot/config",
|
||||
"version": "1.0.0",
|
||||
"description": "Configuration management library for Stock Bot platform",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "bun test",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rimraf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^16.5.0",
|
||||
"yup": "^1.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"typescript": "^5.3.0",
|
||||
"eslint": "^8.56.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
||||
"@typescript-eslint/parser": "^6.19.0",
|
||||
"bun-types": "^1.2.15"
|
||||
},
|
||||
"keywords": [
|
||||
"configuration",
|
||||
"settings",
|
||||
"env",
|
||||
"stock-bot"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"test": "bun test",
|
||||
"clean": "rm -rf dist",
|
||||
"cli": "bun run src/cli.ts",
|
||||
"validate": "bun run src/cli.ts --validate",
|
||||
"check": "bun run src/cli.ts --check"
|
||||
},
|
||||
"bin": {
|
||||
"config-cli": "./dist/cli.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.0.0",
|
||||
"@types/node": "^20.10.5",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bun": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
@echo off
|
||||
echo Building @stock-bot/config library...
|
||||
|
||||
cd /d g:\repos\stock-bot
|
||||
echo Installing dependencies...
|
||||
bun install
|
||||
|
||||
echo Running type check...
|
||||
cd /d g:\repos\stock-bot\libs\config
|
||||
bun run type-check
|
||||
|
||||
echo Running tests...
|
||||
bun test
|
||||
|
||||
echo Setting up example configuration...
|
||||
copy .env.example .env
|
||||
|
||||
echo Running example to display configuration...
|
||||
bun run src/example.ts
|
||||
|
||||
echo.
|
||||
echo Configuration library setup complete!
|
||||
echo.
|
||||
echo You can now import @stock-bot/config in your services.
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
}
|
||||
|
|
@ -1,20 +1,127 @@
|
|||
/**
|
||||
* @stock-bot/config
|
||||
*
|
||||
* Configuration management library for Stock Bot platform using Yup
|
||||
*/
|
||||
// Export all schemas
|
||||
export * from './schemas';
|
||||
|
||||
// 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';
|
||||
// Export types
|
||||
export * from './types';
|
||||
|
||||
// Export errors
|
||||
export * from './errors';
|
||||
|
||||
// Export loaders
|
||||
export { EnvLoader } from './loaders/env.loader';
|
||||
export { FileLoader } from './loaders/file.loader';
|
||||
|
||||
// Export ConfigManager
|
||||
export { ConfigManager } from './config-manager';
|
||||
|
||||
// Export utilities
|
||||
export * from './utils/secrets';
|
||||
export * from './utils/validation';
|
||||
|
||||
// Import necessary types for singleton
|
||||
import { ConfigManager } from './config-manager';
|
||||
import { AppConfig, appConfigSchema } from './schemas';
|
||||
import { FileLoader } from './loaders/file.loader';
|
||||
import { EnvLoader } from './loaders/env.loader';
|
||||
|
||||
// Create singleton instance
|
||||
let configInstance: ConfigManager<AppConfig> | null = null;
|
||||
|
||||
/**
|
||||
* Initialize the global configuration
|
||||
*/
|
||||
export async function initializeConfig(
|
||||
configPath?: string
|
||||
): Promise<AppConfig> {
|
||||
if (!configInstance) {
|
||||
configInstance = new ConfigManager<AppConfig>({
|
||||
configPath,
|
||||
});
|
||||
}
|
||||
return configInstance.initialize(appConfigSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize configuration for a service in a monorepo
|
||||
* Automatically loads configs from:
|
||||
* 1. Root config directory (../../config)
|
||||
* 2. Service-specific config directory (./config)
|
||||
* 3. Environment variables
|
||||
*/
|
||||
export async function initializeServiceConfig(): Promise<AppConfig> {
|
||||
if (!configInstance) {
|
||||
const environment = process.env.NODE_ENV || 'development';
|
||||
configInstance = new ConfigManager<AppConfig>({
|
||||
loaders: [
|
||||
new FileLoader('../../config', environment), // Root config
|
||||
new FileLoader('./config', environment), // Service config
|
||||
new EnvLoader(''), // Environment variables
|
||||
]
|
||||
});
|
||||
}
|
||||
return configInstance.initialize(appConfigSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current configuration
|
||||
*/
|
||||
export function getConfig(): AppConfig {
|
||||
if (!configInstance) {
|
||||
throw new Error('Configuration not initialized. Call initializeConfig() first.');
|
||||
}
|
||||
return configInstance.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration manager instance
|
||||
*/
|
||||
export function getConfigManager(): ConfigManager<AppConfig> {
|
||||
if (!configInstance) {
|
||||
throw new Error('Configuration not initialized. Call initializeConfig() first.');
|
||||
}
|
||||
return configInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset configuration (useful for testing)
|
||||
*/
|
||||
export function resetConfig(): void {
|
||||
if (configInstance) {
|
||||
configInstance.reset();
|
||||
configInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Export convenience functions for common configs
|
||||
export function getDatabaseConfig() {
|
||||
return getConfig().database;
|
||||
}
|
||||
|
||||
export function getServiceConfig() {
|
||||
return getConfig().service;
|
||||
}
|
||||
|
||||
export function getLoggingConfig() {
|
||||
return getConfig().logging;
|
||||
}
|
||||
|
||||
export function getProviderConfig(provider: string) {
|
||||
const providers = getConfig().providers;
|
||||
if (!providers || !(provider in providers)) {
|
||||
throw new Error(`Provider configuration not found: ${provider}`);
|
||||
}
|
||||
return (providers as any)[provider];
|
||||
}
|
||||
|
||||
// Export environment helpers
|
||||
export function isDevelopment(): boolean {
|
||||
return getConfig().environment === 'development';
|
||||
}
|
||||
|
||||
export function isProduction(): boolean {
|
||||
return getConfig().environment === 'production';
|
||||
}
|
||||
|
||||
export function isTest(): boolean {
|
||||
return getConfig().environment === 'test';
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
/**
|
||||
* 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('stock', '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;
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
/**
|
||||
* 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;
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
import {
|
||||
databaseConfig,
|
||||
questdbConfig,
|
||||
mongodbConfig,
|
||||
dragonflyConfig,
|
||||
prometheusConfig,
|
||||
grafanaConfig,
|
||||
lokiConfig,
|
||||
loggingConfig
|
||||
} from './dist/index';
|
||||
|
||||
// Set test environment variables
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.PORT = '3001';
|
||||
|
||||
// Database configs
|
||||
process.env.DB_HOST = 'localhost';
|
||||
process.env.DB_PORT = '5432';
|
||||
process.env.DB_NAME = 'test_db';
|
||||
process.env.DB_USER = 'test_user';
|
||||
process.env.DB_PASSWORD = 'test_pass';
|
||||
|
||||
// QuestDB configs
|
||||
process.env.QUESTDB_HOST = 'localhost';
|
||||
process.env.QUESTDB_HTTP_PORT = '9000';
|
||||
process.env.QUESTDB_PG_PORT = '8812';
|
||||
|
||||
// MongoDB configs
|
||||
process.env.MONGODB_HOST = 'localhost';
|
||||
process.env.MONGODB_PORT = '27017';
|
||||
process.env.MONGODB_DATABASE = 'test_db';
|
||||
|
||||
// Dragonfly configs
|
||||
process.env.DRAGONFLY_HOST = 'localhost';
|
||||
process.env.DRAGONFLY_PORT = '6379';
|
||||
|
||||
// Monitoring configs
|
||||
process.env.PROMETHEUS_HOST = 'localhost';
|
||||
process.env.PROMETHEUS_PORT = '9090';
|
||||
process.env.GRAFANA_HOST = 'localhost';
|
||||
process.env.GRAFANA_PORT = '3000';
|
||||
|
||||
// Loki configs
|
||||
process.env.LOKI_HOST = 'localhost';
|
||||
process.env.LOKI_PORT = '3100';
|
||||
|
||||
// Logging configs
|
||||
process.env.LOG_LEVEL = 'info';
|
||||
process.env.LOG_FORMAT = 'json';
|
||||
|
||||
console.log('🔍 Testing configuration modules...\n');
|
||||
|
||||
const configs = [
|
||||
{ name: 'Database', config: databaseConfig },
|
||||
{ name: 'QuestDB', config: questdbConfig },
|
||||
{ name: 'MongoDB', config: mongodbConfig },
|
||||
{ name: 'Dragonfly', config: dragonflyConfig },
|
||||
{ name: 'Prometheus', config: prometheusConfig },
|
||||
{ name: 'Grafana', config: grafanaConfig },
|
||||
{ name: 'Loki', config: lokiConfig },
|
||||
{ name: 'Logging', config: loggingConfig },
|
||||
];
|
||||
|
||||
let successful = 0;
|
||||
|
||||
for (const { name, config } of configs) {
|
||||
try {
|
||||
if (config && typeof config === 'object' && Object.keys(config).length > 0) {
|
||||
console.log(`✅ ${name}: Loaded successfully`);
|
||||
successful++;
|
||||
} else {
|
||||
console.log(`❌ ${name}: Invalid config object`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`❌ ${name}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n📊 Test Summary: ${successful}/${configs.length} modules loaded successfully`);
|
||||
|
||||
if (successful === configs.length) {
|
||||
console.log('🎉 All configuration modules working correctly!');
|
||||
} else {
|
||||
console.log('⚠️ Some configuration modules have issues.');
|
||||
}
|
||||
|
|
@ -1,445 +0,0 @@
|
|||
/**
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
/**
|
||||
* 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',
|
||||
};
|
||||
}
|
||||
|
|
@ -1,17 +1,22 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022"],
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["bun-types"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
"rootDir": "./src",
|
||||
"declarationDir": "./dist",
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./tsconfig.tsbuildinfo"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/test/**/*",
|
||||
"**/tests/**/*"
|
||||
],
|
||||
"references": [{ "path": "../types" }]
|
||||
}
|
||||
"exclude": ["node_modules", "dist", "test"]
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"extends": ["//"],
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": ["@stock-bot/types#build"],
|
||||
"outputs": ["dist/**"],
|
||||
"inputs": [
|
||||
"src/**",
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
"!**/*.test.ts",
|
||||
"!**/*.spec.ts",
|
||||
"!**/test/**",
|
||||
"!**/tests/**",
|
||||
"!**/__tests__/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Configuration Validation Script
|
||||
* Tests that all configuration modules can be loaded and validated
|
||||
*/
|
||||
|
||||
// Set test environment variables
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.PORT = '3001';
|
||||
|
||||
// Database configs
|
||||
process.env.DB_HOST = 'localhost';
|
||||
process.env.DB_PORT = '5432';
|
||||
process.env.DB_NAME = 'test_db';
|
||||
process.env.DB_USER = 'test_user';
|
||||
process.env.DB_PASSWORD = 'test_pass';
|
||||
|
||||
// QuestDB configs
|
||||
process.env.QUESTDB_HOST = 'localhost';
|
||||
process.env.QUESTDB_HTTP_PORT = '9000';
|
||||
process.env.QUESTDB_PG_PORT = '8812';
|
||||
|
||||
// MongoDB configs
|
||||
process.env.MONGODB_HOST = 'localhost';
|
||||
process.env.MONGODB_PORT = '27017';
|
||||
process.env.MONGODB_DATABASE = 'test_db';
|
||||
|
||||
// Dragonfly configs
|
||||
process.env.DRAGONFLY_HOST = 'localhost';
|
||||
process.env.DRAGONFLY_PORT = '6379';
|
||||
|
||||
// Monitoring configs
|
||||
process.env.PROMETHEUS_HOST = 'localhost';
|
||||
process.env.PROMETHEUS_PORT = '9090';
|
||||
process.env.GRAFANA_HOST = 'localhost';
|
||||
process.env.GRAFANA_PORT = '3000';
|
||||
|
||||
// Loki configs
|
||||
process.env.LOKI_HOST = 'localhost';
|
||||
process.env.LOKI_PORT = '3100';
|
||||
|
||||
// Logging configs
|
||||
process.env.LOG_LEVEL = 'info';
|
||||
process.env.LOG_FORMAT = 'json';
|
||||
|
||||
try {
|
||||
console.log('🔍 Validating configuration modules...\n');
|
||||
|
||||
// Test each configuration module
|
||||
const modules = [
|
||||
{ name: 'Database', path: './dist/database.js' },
|
||||
{ name: 'QuestDB', path: './dist/questdb.js' },
|
||||
{ name: 'MongoDB', path: './dist/mongodb.js' },
|
||||
{ name: 'Dragonfly', path: './dist/dragonfly.js' },
|
||||
{ name: 'Monitoring', path: './dist/monitoring.js' },
|
||||
{ name: 'Loki', path: './dist/loki.js' },
|
||||
{ name: 'Logging', path: './dist/logging.js' },
|
||||
];
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const module of modules) {
|
||||
try {
|
||||
const config = require(module.path);
|
||||
const configKeys = Object.keys(config);
|
||||
|
||||
if (configKeys.length === 0) {
|
||||
throw new Error('No exported configuration found');
|
||||
}
|
||||
|
||||
// Try to access the main config object
|
||||
const mainConfig = config[configKeys[0]];
|
||||
if (!mainConfig || typeof mainConfig !== 'object') {
|
||||
throw new Error('Invalid configuration object');
|
||||
}
|
||||
|
||||
console.log(`✅ ${module.name}: ${configKeys.length} config(s) loaded`);
|
||||
results.push({ name: module.name, status: 'success', configs: configKeys });
|
||||
|
||||
} catch (error) {
|
||||
console.log(`❌ ${module.name}: ${error.message}`);
|
||||
results.push({ name: module.name, status: 'error', error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
// Test main index exports
|
||||
try {
|
||||
const indexExports = require('./dist/index.js');
|
||||
const exportCount = Object.keys(indexExports).length;
|
||||
console.log(`\n✅ Index exports: ${exportCount} modules exported`);
|
||||
results.push({ name: 'Index', status: 'success', exports: exportCount });
|
||||
} catch (error) {
|
||||
console.log(`\n❌ Index exports: ${error.message}`);
|
||||
results.push({ name: 'Index', status: 'error', error: error.message });
|
||||
}
|
||||
|
||||
// Summary
|
||||
const successful = results.filter(r => r.status === 'success').length;
|
||||
const total = results.length;
|
||||
|
||||
console.log(`\n📊 Validation Summary:`);
|
||||
console.log(` Total modules: ${total}`);
|
||||
console.log(` Successful: ${successful}`);
|
||||
console.log(` Failed: ${total - successful}`);
|
||||
|
||||
if (successful === total) {
|
||||
console.log('\n🎉 All configuration modules validated successfully!');
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log('\n⚠️ Some configuration modules failed validation.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Validation script failed:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
import { CacheProvider, createCache } from '@stock-bot/cache';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
import type { QueueManager } from './queue-manager';
|
||||
import type { Queue } from './queue-instance';
|
||||
import type { BatchJobData, BatchResult, JobData, ProcessOptions } from './types';
|
||||
|
||||
const logger = getLogger('batch-processor');
|
||||
|
||||
const cacheProviders = new Map<string, CacheProvider>();
|
||||
|
||||
function getCache(queueName: string): CacheProvider {
|
||||
function getCache(queueName: string, redisConfig: any): CacheProvider {
|
||||
if (!cacheProviders.has(queueName)) {
|
||||
const cacheProvider = createCache({
|
||||
redisConfig,
|
||||
keyPrefix: `batch:${queueName}:`,
|
||||
ttl: 86400, // 24 hours default
|
||||
enableMetrics: true,
|
||||
|
|
@ -23,11 +24,12 @@ function getCache(queueName: string): CacheProvider {
|
|||
* Initialize the batch cache before any batch operations
|
||||
* This should be called during application startup
|
||||
*/
|
||||
export async function initializeBatchCache(queueManager: QueueManager): Promise<void> {
|
||||
const queueName = queueManager.getQueueName();
|
||||
export async function initializeBatchCache(queue: Queue): Promise<void> {
|
||||
const queueName = queue.getName();
|
||||
const redisConfig = queue.getRedisConfig();
|
||||
logger.info('Initializing batch cache...', { queueName });
|
||||
|
||||
const cache = getCache(queueName);
|
||||
const cache = getCache(queueName, redisConfig);
|
||||
await cache.waitForReady(10000);
|
||||
logger.info('Batch cache initialized successfully', { queueName });
|
||||
}
|
||||
|
|
@ -38,7 +40,7 @@ export async function initializeBatchCache(queueManager: QueueManager): Promise<
|
|||
*/
|
||||
export async function processItems<T>(
|
||||
items: T[],
|
||||
queue: QueueManager,
|
||||
queue: Queue,
|
||||
options: ProcessOptions
|
||||
): Promise<BatchResult> {
|
||||
const startTime = Date.now();
|
||||
|
|
@ -83,7 +85,7 @@ export async function processItems<T>(
|
|||
*/
|
||||
async function processDirect<T>(
|
||||
items: T[],
|
||||
queue: QueueManager,
|
||||
queue: Queue,
|
||||
options: ProcessOptions
|
||||
): Promise<Omit<BatchResult, 'duration'>> {
|
||||
const totalDelayMs = options.totalDelayHours * 60 * 60 * 1000; // Convert hours to milliseconds
|
||||
|
|
@ -126,7 +128,7 @@ async function processDirect<T>(
|
|||
*/
|
||||
async function processBatched<T>(
|
||||
items: T[],
|
||||
queue: QueueManager,
|
||||
queue: Queue,
|
||||
options: ProcessOptions
|
||||
): Promise<Omit<BatchResult, 'duration'>> {
|
||||
const batchSize = options.batchSize || 100;
|
||||
|
|
@ -186,7 +188,7 @@ async function processBatched<T>(
|
|||
*/
|
||||
export async function processBatchJob(
|
||||
jobData: BatchJobData,
|
||||
queue: QueueManager
|
||||
queue: Queue
|
||||
): Promise<unknown> {
|
||||
const { payloadKey, batchIndex, totalBatches, itemCount } = jobData;
|
||||
|
||||
|
|
@ -250,14 +252,14 @@ function createBatches<T>(items: T[], batchSize: number): T[][] {
|
|||
|
||||
async function storeItems<T>(
|
||||
items: T[],
|
||||
queue: QueueManager,
|
||||
queue: Queue,
|
||||
options: ProcessOptions
|
||||
): Promise<string> {
|
||||
if (!queue) {
|
||||
throw new Error('Batch cache not initialized. Call initializeBatchCache() first.');
|
||||
}
|
||||
|
||||
const cache = getCache(queue.getQueueName());
|
||||
const cache = getCache(queue.getName(), queue.getRedisConfig());
|
||||
const payloadKey = `payload:${Date.now()}:${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const payload = {
|
||||
|
|
@ -280,7 +282,7 @@ async function storeItems<T>(
|
|||
|
||||
async function loadPayload<T>(
|
||||
key: string,
|
||||
queue: QueueManager
|
||||
queue: Queue
|
||||
): Promise<{
|
||||
items: T[];
|
||||
options: {
|
||||
|
|
@ -295,7 +297,7 @@ async function loadPayload<T>(
|
|||
throw new Error('Batch cache not initialized. Call initializeBatchCache() first.');
|
||||
}
|
||||
|
||||
const cache = getCache(queue.getQueueName());
|
||||
const cache = getCache(queue.getName(), queue.getRedisConfig());
|
||||
return (await cache.get(key)) as {
|
||||
items: T[];
|
||||
options: {
|
||||
|
|
@ -308,17 +310,17 @@ async function loadPayload<T>(
|
|||
} | null;
|
||||
}
|
||||
|
||||
async function cleanupPayload(key: string, queue: QueueManager): Promise<void> {
|
||||
async function cleanupPayload(key: string, queue: Queue): Promise<void> {
|
||||
if (!queue) {
|
||||
throw new Error('Batch cache not initialized. Call initializeBatchCache() first.');
|
||||
}
|
||||
|
||||
const cache = getCache(queue.getQueueName());
|
||||
const cache = getCache(queue.getName(), queue.getRedisConfig());
|
||||
await cache.del(key);
|
||||
}
|
||||
|
||||
async function addJobsInChunks(
|
||||
queue: QueueManager,
|
||||
queue: Queue,
|
||||
jobs: Array<{ name: string; data: JobData; opts?: Record<string, unknown> }>,
|
||||
chunkSize = 100
|
||||
): Promise<unknown[]> {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,28 @@
|
|||
export * from './batch-processor';
|
||||
export * from './provider-registry';
|
||||
export * from './queue-manager';
|
||||
export * from './queue-instance';
|
||||
export * from './queue-factory';
|
||||
export * from './types';
|
||||
|
||||
// Re-export commonly used functions
|
||||
export { initializeBatchCache, processBatchJob, processItems } from './batch-processor';
|
||||
|
||||
export { QueueManager } from './queue-manager';
|
||||
export { Queue } from './queue-instance';
|
||||
|
||||
export { providerRegistry } from './provider-registry';
|
||||
|
||||
// Re-export queue factory functions
|
||||
export {
|
||||
initializeQueueSystem,
|
||||
getQueue,
|
||||
processItemsWithQueue,
|
||||
getActiveQueueNames,
|
||||
getQueueManager,
|
||||
shutdownAllQueues
|
||||
} from './queue-factory';
|
||||
|
||||
// Re-export types for convenience
|
||||
export type {
|
||||
BatchResult,
|
||||
|
|
|
|||
112
libs/queue/src/queue-factory.ts
Normal file
112
libs/queue/src/queue-factory.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import { getLogger } from '@stock-bot/logger';
|
||||
import { QueueManager } from './queue-manager';
|
||||
import { Queue } from './queue-instance';
|
||||
import type { ProcessOptions, BatchResult } from './types';
|
||||
|
||||
const logger = getLogger('queue-factory');
|
||||
|
||||
// Global queue manager (manages workers and providers)
|
||||
let queueManager: QueueManager | null = null;
|
||||
// Registry of individual queues
|
||||
const queues = new Map<string, Queue>();
|
||||
let globalRedisConfig: any = null;
|
||||
|
||||
/**
|
||||
* Initialize the queue system with global configuration
|
||||
*/
|
||||
export async function initializeQueueSystem(config: {
|
||||
redis: any;
|
||||
defaultJobOptions?: any;
|
||||
workers?: number;
|
||||
concurrency?: number;
|
||||
}): Promise<void> {
|
||||
logger.info('Initializing global queue system...');
|
||||
|
||||
globalRedisConfig = config.redis;
|
||||
|
||||
// Initialize the global queue manager for worker management
|
||||
queueManager = new QueueManager({
|
||||
queueName: 'system-queue-manager',
|
||||
redis: globalRedisConfig,
|
||||
workers: config.workers || 5,
|
||||
concurrency: config.concurrency || 20,
|
||||
defaultJobOptions: config.defaultJobOptions,
|
||||
providers: [], // Will be set by individual services
|
||||
});
|
||||
|
||||
await queueManager.initialize();
|
||||
|
||||
logger.info('Queue system initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a queue for the given queue name
|
||||
*/
|
||||
export function getQueue(queueName: string): Queue {
|
||||
if (!globalRedisConfig) {
|
||||
throw new Error('Queue system not initialized. Call initializeQueueSystem() first.');
|
||||
}
|
||||
|
||||
if (!queues.has(queueName)) {
|
||||
logger.info(`Creating new queue: ${queueName}`);
|
||||
|
||||
const queue = new Queue(queueName, globalRedisConfig);
|
||||
queues.set(queueName, queue);
|
||||
}
|
||||
|
||||
return queues.get(queueName)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process items using the specified queue
|
||||
*/
|
||||
export async function processItemsWithQueue<T>(
|
||||
queueName: string,
|
||||
items: T[],
|
||||
options: ProcessOptions
|
||||
): Promise<BatchResult> {
|
||||
const queue = getQueue(queueName);
|
||||
return queue.processItems(items, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active queue names
|
||||
*/
|
||||
export function getActiveQueueNames(): string[] {
|
||||
return Array.from(queues.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global queue manager (for advanced operations)
|
||||
*/
|
||||
export function getQueueManager(): QueueManager {
|
||||
if (!queueManager) {
|
||||
throw new Error('Queue system not initialized. Call initializeQueueSystem() first.');
|
||||
}
|
||||
return queueManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown all queues and the queue manager
|
||||
*/
|
||||
export async function shutdownAllQueues(): Promise<void> {
|
||||
logger.info('Shutting down all queues...');
|
||||
|
||||
// Shutdown individual queues
|
||||
const queueShutdownPromises = Array.from(queues.values()).map(queue =>
|
||||
queue.shutdown().catch(error => {
|
||||
logger.error('Error shutting down queue', { error });
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(queueShutdownPromises);
|
||||
queues.clear();
|
||||
|
||||
// Shutdown the global queue manager
|
||||
if (queueManager) {
|
||||
await queueManager.shutdown();
|
||||
queueManager = null;
|
||||
}
|
||||
|
||||
logger.info('All queues shut down');
|
||||
}
|
||||
186
libs/queue/src/queue-instance.ts
Normal file
186
libs/queue/src/queue-instance.ts
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
import { Queue as BullQueue, Worker, QueueEvents, type Job } from 'bullmq';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
import { processItems } from './batch-processor';
|
||||
import type { JobData, ProcessOptions, BatchResult } from './types';
|
||||
|
||||
const logger = getLogger('queue-instance');
|
||||
|
||||
export class Queue {
|
||||
private bullQueue: BullQueue;
|
||||
private workers: Worker[] = [];
|
||||
private queueEvents: QueueEvents;
|
||||
private queueName: string;
|
||||
private redisConfig: any;
|
||||
private initialized = false;
|
||||
|
||||
constructor(queueName: string, redisConfig: any) {
|
||||
this.queueName = queueName;
|
||||
this.redisConfig = redisConfig;
|
||||
|
||||
const connection = {
|
||||
host: redisConfig.host,
|
||||
port: redisConfig.port,
|
||||
password: redisConfig.password,
|
||||
db: redisConfig.db,
|
||||
};
|
||||
|
||||
// Initialize BullMQ queue
|
||||
this.bullQueue = new BullQueue(`{${queueName}}`, {
|
||||
connection,
|
||||
defaultJobOptions: {
|
||||
removeOnComplete: 10,
|
||||
removeOnFail: 5,
|
||||
attempts: 3,
|
||||
backoff: {
|
||||
type: 'exponential',
|
||||
delay: 1000,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Initialize queue events
|
||||
this.queueEvents = new QueueEvents(`{${queueName}}`, { connection });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the queue name
|
||||
*/
|
||||
getName(): string {
|
||||
return this.queueName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the redis configuration
|
||||
*/
|
||||
getRedisConfig(): any {
|
||||
return this.redisConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize batch cache for this queue
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { initializeBatchCache } = await import('./batch-processor');
|
||||
await initializeBatchCache(this);
|
||||
this.initialized = true;
|
||||
|
||||
logger.info(`Queue initialized: ${this.queueName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process items using this queue
|
||||
*/
|
||||
async processItems<T>(items: T[], options: ProcessOptions): Promise<BatchResult> {
|
||||
// Ensure queue is initialized
|
||||
if (!this.initialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
return processItems(items, this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single job to the queue
|
||||
*/
|
||||
async add(name: string, data: JobData, options: Record<string, unknown> = {}): Promise<Job> {
|
||||
return await this.bullQueue.add(name, data, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple jobs to the queue in bulk
|
||||
*/
|
||||
async addBulk(
|
||||
jobs: Array<{ name: string; data: JobData; opts?: Record<string, unknown> }>
|
||||
): Promise<Job[]> {
|
||||
return await this.bullQueue.addBulk(jobs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queue statistics
|
||||
*/
|
||||
async getStats(): Promise<{
|
||||
waiting: number;
|
||||
active: number;
|
||||
completed: number;
|
||||
failed: number;
|
||||
delayed: number;
|
||||
}> {
|
||||
const [waiting, active, completed, failed, delayed] = await Promise.all([
|
||||
this.bullQueue.getWaiting(),
|
||||
this.bullQueue.getActive(),
|
||||
this.bullQueue.getCompleted(),
|
||||
this.bullQueue.getFailed(),
|
||||
this.bullQueue.getDelayed(),
|
||||
]);
|
||||
|
||||
return {
|
||||
waiting: waiting.length,
|
||||
active: active.length,
|
||||
completed: completed.length,
|
||||
failed: failed.length,
|
||||
delayed: delayed.length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the queue
|
||||
*/
|
||||
async pause(): Promise<void> {
|
||||
await this.bullQueue.pause();
|
||||
logger.info(`Queue paused: ${this.queueName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume the queue
|
||||
*/
|
||||
async resume(): Promise<void> {
|
||||
await this.bullQueue.resume();
|
||||
logger.info(`Queue resumed: ${this.queueName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean completed and failed jobs
|
||||
*/
|
||||
async clean(grace: number = 0, limit: number = 100): Promise<void> {
|
||||
await Promise.all([
|
||||
this.bullQueue.clean(grace, limit, 'completed'),
|
||||
this.bullQueue.clean(grace, limit, 'failed'),
|
||||
]);
|
||||
logger.info(`Queue cleaned: ${this.queueName}`, { grace, limit });
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown this queue
|
||||
*/
|
||||
async shutdown(): Promise<void> {
|
||||
logger.info(`Shutting down queue: ${this.queueName}`);
|
||||
|
||||
try {
|
||||
// Close workers
|
||||
await Promise.all(this.workers.map(worker => worker.close()));
|
||||
this.workers = [];
|
||||
|
||||
// Close queue events
|
||||
await this.queueEvents.close();
|
||||
|
||||
// Close queue
|
||||
await this.bullQueue.close();
|
||||
|
||||
logger.info(`Queue shutdown complete: ${this.queueName}`);
|
||||
} catch (error) {
|
||||
logger.error(`Error during queue shutdown: ${this.queueName}`, { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the BullMQ queue instance (for advanced operations)
|
||||
*/
|
||||
getBullQueue(): BullQueue {
|
||||
return this.bullQueue;
|
||||
}
|
||||
}
|
||||
|
|
@ -285,6 +285,13 @@ export class QueueManager {
|
|||
return this.config.queueName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the redis configuration
|
||||
*/
|
||||
getRedisConfig(): any {
|
||||
return this.config.redis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the queue manager
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ trap cleanup EXIT
|
|||
libs=(
|
||||
"types" # Base types - no dependencies
|
||||
"config" # Configuration - depends on types
|
||||
"config-new" # Configuration - depends on types
|
||||
"config" # Configuration - depends on types
|
||||
"logger" # Logging utilities - depends on types
|
||||
"utils" # Utilities - depends on types and config
|
||||
# Database clients
|
||||
|
|
|
|||
|
|
@ -60,9 +60,7 @@
|
|||
|
||||
// Applications
|
||||
{ "path": "./apps/data-service" },
|
||||
{ "path": "./apps/execution-service" },
|
||||
{ "path": "./apps/portfolio-service" },
|
||||
{ "path": "./apps/processing-service" },
|
||||
{ "path": "./apps/strategy-service" }
|
||||
{ "path": "./apps/data-sync-service" },
|
||||
{ "path": "./apps/web-api" }
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue