added disabled functioality

This commit is contained in:
Boki 2025-06-22 12:35:32 -04:00
parent fabf42dc7f
commit d8ae0cb3c5
16 changed files with 147 additions and 75 deletions

4
.env
View file

@ -4,8 +4,8 @@
# Core Application Settings # Core Application Settings
NODE_ENV=development NODE_ENV=development
LOG_LEVEL=debug LOG_LEVEL=trace
LOG_HIDE_OBJECT=false LOG_HIDE_OBJECT=true
# Data Service Configuration # Data Service Configuration
DATA_SERVICE_PORT=2001 DATA_SERVICE_PORT=2001

View file

@ -3,7 +3,7 @@ import { z } from 'zod';
import { EnvLoader } from './loaders/env.loader'; import { EnvLoader } from './loaders/env.loader';
import { FileLoader } from './loaders/file.loader'; import { FileLoader } from './loaders/file.loader';
import { ConfigError, ConfigValidationError } from './errors'; import { ConfigError, ConfigValidationError } from './errors';
import { import type {
ConfigLoader, ConfigLoader,
ConfigManagerOptions, ConfigManagerOptions,
ConfigSchema, ConfigSchema,

View file

@ -2,7 +2,8 @@
import { EnvLoader } from './loaders/env.loader'; import { EnvLoader } from './loaders/env.loader';
import { FileLoader } from './loaders/file.loader'; import { FileLoader } from './loaders/file.loader';
import { ConfigManager } from './config-manager'; import { ConfigManager } from './config-manager';
import { AppConfig, appConfigSchema } from './schemas'; import type { AppConfig } from './schemas';
import { appConfigSchema } from './schemas';
// Create singleton instance // Create singleton instance
let configInstance: ConfigManager<AppConfig> | null = null; let configInstance: ConfigManager<AppConfig> | null = null;

View file

@ -1,6 +1,6 @@
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { ConfigLoaderError } from '../errors'; import { ConfigLoaderError } from '../errors';
import { ConfigLoader } from '../types'; import type { ConfigLoader } from '../types';
export interface EnvLoaderOptions { export interface EnvLoaderOptions {
convertCase?: boolean; convertCase?: boolean;

View file

@ -1,7 +1,7 @@
import { existsSync, readFileSync } from 'fs'; import { existsSync, readFileSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { ConfigLoaderError } from '../errors'; import { ConfigLoaderError } from '../errors';
import { ConfigLoader } from '../types'; import type { ConfigLoader } from '../types';
export class FileLoader implements ConfigLoader { export class FileLoader implements ConfigLoader {
readonly priority = 50; // Medium priority readonly priority = 50; // Medium priority

View file

@ -2,6 +2,7 @@ import { z } from 'zod';
// PostgreSQL configuration // PostgreSQL configuration
export const postgresConfigSchema = z.object({ export const postgresConfigSchema = z.object({
enabled: z.boolean().default(true),
host: z.string().default('localhost'), host: z.string().default('localhost'),
port: z.number().default(5432), port: z.number().default(5432),
database: z.string(), database: z.string(),
@ -15,6 +16,7 @@ export const postgresConfigSchema = z.object({
// QuestDB configuration // QuestDB configuration
export const questdbConfigSchema = z.object({ export const questdbConfigSchema = z.object({
enabled: z.boolean().default(true),
host: z.string().default('localhost'), host: z.string().default('localhost'),
ilpPort: z.number().default(9009), ilpPort: z.number().default(9009),
httpPort: z.number().default(9000), httpPort: z.number().default(9000),
@ -28,6 +30,7 @@ export const questdbConfigSchema = z.object({
// MongoDB configuration // MongoDB configuration
export const mongodbConfigSchema = z.object({ export const mongodbConfigSchema = z.object({
enabled: z.boolean().default(true),
uri: z.string().url().optional(), uri: z.string().url().optional(),
host: z.string().default('localhost'), host: z.string().default('localhost'),
port: z.number().default(27017), port: z.number().default(27017),
@ -41,6 +44,7 @@ export const mongodbConfigSchema = z.object({
// Dragonfly/Redis configuration // Dragonfly/Redis configuration
export const dragonflyConfigSchema = z.object({ export const dragonflyConfigSchema = z.object({
enabled: z.boolean().default(true),
host: z.string().default('localhost'), host: z.string().default('localhost'),
port: z.number().default(6379), port: z.number().default(6379),
password: z.string().optional(), password: z.string().optional(),

View file

@ -16,6 +16,7 @@ import { asFunction, asValue, createContainer, InjectionMode, type AwilixContain
// Configuration types // Configuration types
export interface AppConfig { export interface AppConfig {
redis: { redis: {
enabled?: boolean;
host: string; host: string;
port: number; port: number;
password?: string; password?: string;
@ -23,10 +24,12 @@ export interface AppConfig {
db?: number; db?: number;
}; };
mongodb: { mongodb: {
enabled?: boolean;
uri: string; uri: string;
database: string; database: string;
}; };
postgres: { postgres: {
enabled?: boolean;
host: string; host: string;
port: number; port: number;
database: string; database: string;
@ -34,6 +37,7 @@ export interface AppConfig {
password: string; password: string;
}; };
questdb?: { questdb?: {
enabled?: boolean;
host: string; host: string;
httpPort?: number; httpPort?: number;
pgPort?: number; pgPort?: number;
@ -59,7 +63,7 @@ export function createServiceContainer(config: AppConfig): AwilixContainer {
}); });
// Register configuration values // Register configuration values
container.register({ const registrations: any = {
// Configuration // Configuration
config: asValue(config), config: asValue(config),
redisConfig: asValue(config.redis), redisConfig: asValue(config.redis),
@ -69,9 +73,11 @@ export function createServiceContainer(config: AppConfig): AwilixContainer {
// Core services with dependency injection // Core services with dependency injection
logger: asFunction(() => getLogger('app')).singleton(), logger: asFunction(() => getLogger('app')).singleton(),
};
// Cache with injected config and logger // Conditionally register cache/dragonfly
cache: asFunction(({ redisConfig, logger }) => if (config.redis?.enabled !== false) {
registrations.cache = asFunction(({ redisConfig, logger }) =>
createCache({ createCache({
redisConfig, redisConfig,
logger, logger,
@ -79,23 +85,37 @@ export function createServiceContainer(config: AppConfig): AwilixContainer {
ttl: 3600, ttl: 3600,
enableMetrics: true, enableMetrics: true,
}) })
).singleton(), ).singleton();
} else {
registrations.cache = asValue(null);
}
// Proxy manager with injected cache and logger // Proxy manager depends on cache
proxyManager: asFunction(({ cache, config, logger }) => { registrations.proxyManager = asFunction(({ cache, config, logger }) => {
if (!cache) {
logger.warn('Cache is disabled, ProxyManager will have limited functionality');
return null;
}
const manager = new ProxyManager( const manager = new ProxyManager(
cache, cache,
config.proxy || {}, config.proxy || {},
logger logger
); );
// Note: initialization happens in initializeServices function
return manager; return manager;
}).singleton(), // MongoDB client with injected dependencies }).singleton();
mongoClient: asFunction(({ mongoConfig, logger }) => {
return new MongoDBClient(mongoConfig, logger);
}).singleton(),
postgresClient: asFunction(({ postgresConfig, logger }) => { // Conditionally register MongoDB client
if (config.mongodb?.enabled !== false) {
registrations.mongoClient = asFunction(({ mongoConfig, logger }) => {
return new MongoDBClient(mongoConfig, logger);
}).singleton();
} else {
registrations.mongoClient = asValue(null);
}
// Conditionally register PostgreSQL client
if (config.postgres?.enabled !== false) {
registrations.postgresClient = asFunction(({ postgresConfig, logger }) => {
return createPostgreSQLClient( return createPostgreSQLClient(
{ {
host: postgresConfig.host, host: postgresConfig.host,
@ -106,9 +126,14 @@ export function createServiceContainer(config: AppConfig): AwilixContainer {
}, },
logger logger
); );
}).singleton(), }).singleton();
} else {
registrations.postgresClient = asValue(null);
}
questdbClient: asFunction(({ questdbConfig, logger }) => { // Conditionally register QuestDB client
if (config.questdb?.enabled !== false) {
registrations.questdbClient = asFunction(({ questdbConfig, logger }) => {
console.log('Creating QuestDB client with config:', questdbConfig); console.log('Creating QuestDB client with config:', questdbConfig);
return createQuestDBClient( return createQuestDBClient(
{ {
@ -123,21 +148,24 @@ export function createServiceContainer(config: AppConfig): AwilixContainer {
}, },
logger logger
); );
}).singleton(), }).singleton();
} else {
registrations.questdbClient = asValue(null);
}
// Queue manager - placeholder // Queue manager - placeholder
queueManager: asFunction(() => { registrations.queueManager = asFunction(() => {
// TODO: Create queue manager when decoupled // TODO: Create queue manager when decoupled
return null; return null;
}).singleton(), }).singleton();
// Browser automation // Browser automation
browser: asFunction(({ config, logger }) => { registrations.browser = asFunction(({ config, logger }) => {
return new Browser(logger, config.browser); return new Browser(logger, config.browser);
}).singleton(), }).singleton();
// Build the IServiceContainer for handlers // Build the IServiceContainer for handlers
serviceContainer: asFunction((cradle) => ({ registrations.serviceContainer = asFunction((cradle) => ({
logger: cradle.logger, logger: cradle.logger,
cache: cradle.cache, cache: cradle.cache,
proxy: cradle.proxyManager, proxy: cradle.proxyManager,
@ -146,9 +174,9 @@ export function createServiceContainer(config: AppConfig): AwilixContainer {
postgres: cradle.postgresClient, postgres: cradle.postgresClient,
questdb: cradle.questdbClient, questdb: cradle.questdbClient,
queue: cradle.queueManager, queue: cradle.queueManager,
} as IServiceContainer)).singleton(), } as IServiceContainer)).singleton();
});
container.register(registrations);
return container; return container;
} }
@ -157,40 +185,53 @@ export function createServiceContainer(config: AppConfig): AwilixContainer {
*/ */
export async function initializeServices(container: AwilixContainer): Promise<void> { export async function initializeServices(container: AwilixContainer): Promise<void> {
const logger = container.resolve('logger'); const logger = container.resolve('logger');
const config = container.resolve('config');
try { try {
// Wait for cache to be ready first // Wait for cache to be ready first (if enabled)
const cache = container.resolve('cache'); const cache = container.resolve('cache');
if (cache && typeof cache.waitForReady === 'function') { if (cache && typeof cache.waitForReady === 'function') {
await cache.waitForReady(10000); await cache.waitForReady(10000);
logger.info('Cache is ready'); logger.info('Cache is ready');
} else if (config.redis?.enabled === false) {
logger.info('Cache is disabled');
} }
// Initialize proxy manager // Initialize proxy manager (depends on cache)
const proxyManager = container.resolve('proxyManager'); const proxyManager = container.resolve('proxyManager');
if (proxyManager && typeof proxyManager.initialize === 'function') { if (proxyManager && typeof proxyManager.initialize === 'function') {
await proxyManager.initialize(); await proxyManager.initialize();
logger.info('Proxy manager initialized'); logger.info('Proxy manager initialized');
} else {
logger.info('Proxy manager is disabled (requires cache)');
} }
// Connect database clients // Connect MongoDB client (if enabled)
const mongoClient = container.resolve('mongoClient'); const mongoClient = container.resolve('mongoClient');
if (mongoClient && typeof mongoClient.connect === 'function') { if (mongoClient && typeof mongoClient.connect === 'function') {
await mongoClient.connect(); await mongoClient.connect();
logger.info('MongoDB connected'); logger.info('MongoDB connected');
} else if (config.mongodb?.enabled === false) {
logger.info('MongoDB is disabled');
} }
// Connect PostgreSQL client (if enabled)
const postgresClient = container.resolve('postgresClient'); const postgresClient = container.resolve('postgresClient');
if (postgresClient && typeof postgresClient.connect === 'function') { if (postgresClient && typeof postgresClient.connect === 'function') {
await postgresClient.connect(); await postgresClient.connect();
logger.info('PostgreSQL connected'); logger.info('PostgreSQL connected');
} else if (config.postgres?.enabled === false) {
logger.info('PostgreSQL is disabled');
} }
// const questdbClient = container.resolve('questdbClient'); // Connect QuestDB client (if enabled)
// if (questdbClient && typeof questdbClient.connect === 'function') { const questdbClient = container.resolve('questdbClient');
// await questdbClient.connect(); if (questdbClient && typeof questdbClient.connect === 'function') {
// logger.info('QuestDB connected'); await questdbClient.connect();
// } logger.info('QuestDB connected');
} else if (config.questdb?.enabled === false) {
logger.info('QuestDB is disabled');
}
// Initialize browser if configured // Initialize browser if configured
const browser = container.resolve('browser'); const browser = container.resolve('browser');

View file

@ -76,6 +76,22 @@ export function QueueSchedule(
}; };
} }
/**
* Disabled decorator - marks a handler as disabled for auto-registration
* Handlers marked with @Disabled() will be skipped during auto-registration
*/
export function Disabled() {
return function <T extends { new (...args: any[]): {} }>(
target: T,
_context?: any
) {
// Store disabled flag on the constructor
(target as any).__disabled = true;
return target;
};
}
/** /**
* Combined decorator for scheduled operations * Combined decorator for scheduled operations
* Automatically creates both an operation and a schedule * Automatically creates both an operation and a schedule

View file

@ -22,7 +22,7 @@ export type { IServiceContainer } from './types/service-container';
export { createJobHandler } from './types/types'; export { createJobHandler } from './types/types';
// Decorators // Decorators
export { Handler, Operation, QueueSchedule, ScheduledOperation } from './decorators/decorators'; export { Handler, Operation, QueueSchedule, ScheduledOperation, Disabled } from './decorators/decorators';
// Auto-registration utilities // Auto-registration utilities
export { autoRegisterHandlers, createAutoHandlerRegistry } from './registry/auto-register'; export { autoRegisterHandlers, createAutoHandlerRegistry } from './registry/auto-register';

View file

@ -108,6 +108,12 @@ export async function autoRegisterHandlers(
for (const HandlerClass of handlerClasses) { for (const HandlerClass of handlerClasses) {
const handlerName = HandlerClass.name; const handlerName = HandlerClass.name;
// Check if handler is disabled
if ((HandlerClass as any).__disabled) {
logger.info(`Skipping disabled handler: ${handlerName} from ${relativePath}`);
continue;
}
if (dryRun) { if (dryRun) {
logger.info(`[DRY RUN] Would register handler: ${handlerName} from ${relativePath}`); logger.info(`[DRY RUN] Would register handler: ${handlerName} from ${relativePath}`);
registered.push(handlerName); registered.push(handlerName);

View file

@ -12,15 +12,15 @@ import type { ProxyManager } from '@stock-bot/proxy';
export interface IServiceContainer { export interface IServiceContainer {
// Core infrastructure // Core infrastructure
readonly logger: any; // Logger instance readonly logger: any; // Logger instance
readonly cache: any; // Cache provider (Redis/Dragonfly) readonly cache?: any; // Cache provider (Redis/Dragonfly) - optional
readonly queue: any; // Queue manager (BullMQ) readonly queue?: any; // Queue manager (BullMQ) - optional
readonly proxy: ProxyManager; // Proxy manager service readonly proxy?: ProxyManager; // Proxy manager service - optional (depends on cache)
readonly browser?: any; // Browser automation (Playwright) readonly browser?: any; // Browser automation (Playwright)
// Database clients // Database clients - all optional to support selective enabling
readonly mongodb: any; // MongoDB client readonly mongodb?: any; // MongoDB client
readonly postgres: any; // PostgreSQL client readonly postgres?: any; // PostgreSQL client
readonly questdb: any; // QuestDB client (time-series) readonly questdb?: any; // QuestDB client (time-series)
// Optional extensions for future use // Optional extensions for future use
readonly custom?: Record<string, any>; readonly custom?: Record<string, any>;

View file

@ -1,6 +1,6 @@
import Redis from 'ioredis'; import Redis from 'ioredis';
import { RedisConnectionManager } from './connection-manager'; import { RedisConnectionManager } from './connection-manager';
import { CacheOptions, CacheProvider, CacheStats } from './types'; import type { CacheOptions, CacheProvider, CacheStats } from './types';
/** /**
* Simplified Redis-based cache provider using connection manager * Simplified Redis-based cache provider using connection manager

View file

@ -1,5 +1,6 @@
import type { Logger } from '@stock-bot/core/logger'; import type { Logger } from '@stock-bot/core/logger';
import { Collection, Db, MongoClient, OptionalUnlessRequiredId } from 'mongodb'; import { Collection, Db, MongoClient } from 'mongodb';
import type { OptionalUnlessRequiredId } from 'mongodb';
import type { ConnectionEvents, DocumentBase, DynamicPoolConfig, MongoDBClientConfig, PoolMetrics } from './types'; import type { ConnectionEvents, DocumentBase, DynamicPoolConfig, MongoDBClientConfig, PoolMetrics } from './types';
/** /**

View file

@ -1,4 +1,5 @@
import { Pool, QueryResultRow } from 'pg'; import { Pool } from 'pg';
import type { QueryResultRow } from 'pg';
import { PostgreSQLHealthMonitor } from './health'; import { PostgreSQLHealthMonitor } from './health';
import { PostgreSQLQueryBuilder } from './query-builder'; import { PostgreSQLQueryBuilder } from './query-builder';
import { PostgreSQLTransactionManager } from './transactions'; import { PostgreSQLTransactionManager } from './transactions';

View file

@ -1,4 +1,5 @@
import { BrowserContext, chromium, Page, Browser as PlaywrightBrowser } from 'playwright'; import { chromium } from 'playwright';
import type { BrowserContext, Page, Browser as PlaywrightBrowser } from 'playwright';
import type { BrowserOptions, NetworkEvent, NetworkEventHandler } from './types'; import type { BrowserOptions, NetworkEvent, NetworkEventHandler } from './types';
export class Browser { export class Browser {

View file

@ -1,4 +1,5 @@
import { CacheProvider, createCache } from '@stock-bot/cache'; import { createCache } from '@stock-bot/cache';
import type { CacheProvider } from '@stock-bot/cache';
import { getLogger } from '@stock-bot/logger'; import { getLogger } from '@stock-bot/logger';
import { Queue, type QueueWorkerConfig } from './queue'; import { Queue, type QueueWorkerConfig } from './queue';
import { QueueRateLimiter } from './rate-limiter'; import { QueueRateLimiter } from './rate-limiter';