dependency hell fixes

This commit is contained in:
Boki 2025-06-23 19:07:17 -04:00
parent 50e5b5cbed
commit 24768446f5
20 changed files with 219 additions and 197 deletions

View file

@ -9,7 +9,8 @@
}, },
"dependencies": { "dependencies": {
"@stock-bot/config": "workspace:*", "@stock-bot/config": "workspace:*",
"@stock-bot/logger": "workspace:*" "@stock-bot/logger": "workspace:*",
"@stock-bot/handlers": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@types/pg": "^8.10.7" "@types/pg": "^8.10.7"

View file

@ -6,7 +6,7 @@
import type { Browser } from '@stock-bot/browser'; import type { Browser } from '@stock-bot/browser';
import type { CacheProvider } from '@stock-bot/cache'; import type { CacheProvider } from '@stock-bot/cache';
import type { AppConfig as StockBotAppConfig } from '@stock-bot/config'; import type { AppConfig as StockBotAppConfig } from '@stock-bot/config';
import type { IServiceContainer } from '@stock-bot/handlers'; import type { IServiceContainer } from '@stock-bot/types';
import type { Logger } from '@stock-bot/logger'; import type { Logger } from '@stock-bot/logger';
import type { MongoDBClient } from '@stock-bot/mongodb'; import type { MongoDBClient } from '@stock-bot/mongodb';
import type { PostgreSQLClient } from '@stock-bot/postgres'; import type { PostgreSQLClient } from '@stock-bot/postgres';

View file

@ -1,6 +1,6 @@
import type { Browser } from '@stock-bot/browser'; import type { Browser } from '@stock-bot/browser';
import type { CacheProvider } from '@stock-bot/cache'; import type { CacheProvider } from '@stock-bot/cache';
import type { IServiceContainer } from '@stock-bot/handlers'; import type { IServiceContainer } from '@stock-bot/types';
import type { Logger } from '@stock-bot/logger'; import type { Logger } from '@stock-bot/logger';
import type { MongoDBClient } from '@stock-bot/mongodb'; import type { MongoDBClient } from '@stock-bot/mongodb';
import type { PostgreSQLClient } from '@stock-bot/postgres'; import type { PostgreSQLClient } from '@stock-bot/postgres';

View file

@ -9,7 +9,7 @@ import { getLogger, setLoggerConfig, shutdownLoggers, type Logger } from '@stock
import { Shutdown } from '@stock-bot/shutdown'; import { Shutdown } from '@stock-bot/shutdown';
import type { AppConfig as StockBotAppConfig, UnifiedAppConfig } from '@stock-bot/config'; import type { AppConfig as StockBotAppConfig, UnifiedAppConfig } from '@stock-bot/config';
import { toUnifiedConfig } from '@stock-bot/config'; import { toUnifiedConfig } from '@stock-bot/config';
import type { IServiceContainer } from '@stock-bot/handlers'; import type { IServiceContainer } from '@stock-bot/types';
import type { ServiceContainer } from './awilix-container'; import type { ServiceContainer } from './awilix-container';
/** /**
@ -332,7 +332,7 @@ export class ServiceApplication {
} }
this.logger.debug('Creating scheduled jobs from registered handlers...'); this.logger.debug('Creating scheduled jobs from registered handlers...');
const { handlerRegistry } = await import('@stock-bot/types'); const { handlerRegistry } = await import('@stock-bot/handlers');
const allHandlers = handlerRegistry.getAllHandlersWithSchedule(); const allHandlers = handlerRegistry.getAllHandlersWithSchedule();
let totalScheduledJobs = 0; let totalScheduledJobs = 0;

View file

@ -12,8 +12,7 @@
"dependencies": { "dependencies": {
"@stock-bot/config": "workspace:*", "@stock-bot/config": "workspace:*",
"@stock-bot/logger": "workspace:*", "@stock-bot/logger": "workspace:*",
"@stock-bot/types": "workspace:*", "@stock-bot/types": "workspace:*"
"@stock-bot/di": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.11.0", "@types/node": "^20.11.0",

View file

@ -1,13 +1,11 @@
import type { Collection } from 'mongodb'; import type { Collection } from 'mongodb';
import { getLogger } from '@stock-bot/logger'; import { getLogger } from '@stock-bot/logger';
import { import type { HandlerConfigWithSchedule } from '@stock-bot/types';
createJobHandler,
handlerRegistry,
type HandlerConfigWithSchedule,
} from '@stock-bot/types';
import { fetch } from '@stock-bot/utils'; import { fetch } from '@stock-bot/utils';
import { createNamespacedCache } from '@stock-bot/cache'; import { createNamespacedCache } from '@stock-bot/cache';
import type { IServiceContainer } from '../types/service-container'; import type { IServiceContainer } from '@stock-bot/types';
import { handlerRegistry } from '../registry/handler-registry';
import { createJobHandler } from '../utils/create-job-handler';
import type { ExecutionContext, IHandler } from '../types/types'; import type { ExecutionContext, IHandler } from '../types/types';
/** /**

View file

@ -2,8 +2,11 @@
export { BaseHandler, ScheduledHandler } from './base/BaseHandler'; export { BaseHandler, ScheduledHandler } from './base/BaseHandler';
export type { JobScheduleOptions } from './base/BaseHandler'; export type { JobScheduleOptions } from './base/BaseHandler';
// Handler registry (re-exported from types to avoid circular deps) // Handler registry
export { handlerRegistry } from '@stock-bot/types'; export { handlerRegistry } from './registry/handler-registry';
// Utilities
export { createJobHandler } from './utils/create-job-handler';
// Types // Types
export type { export type {
@ -18,9 +21,8 @@ export type {
OperationMetadata, OperationMetadata,
} from './types/types'; } from './types/types';
export type { IServiceContainer } from './types/service-container'; // Re-export IServiceContainer from types package
export type { IServiceContainer } from '@stock-bot/types';
export { createJobHandler } from './types/types';
// Decorators // Decorators
export { export {

View file

@ -7,7 +7,7 @@ import { readdirSync, statSync } from 'fs';
import { join, relative } from 'path'; import { join, relative } from 'path';
import { getLogger } from '@stock-bot/logger'; import { getLogger } from '@stock-bot/logger';
import { BaseHandler } from '../base/BaseHandler'; import { BaseHandler } from '../base/BaseHandler';
import type { IServiceContainer } from '../types/service-container'; import type { IServiceContainer } from '@stock-bot/types';
const logger = getLogger('handler-auto-register'); const logger = getLogger('handler-auto-register');

View file

@ -1,121 +1,143 @@
/** /**
* Handler Registry - Lightweight registry for queue handlers * Handler Registry - Runtime registry for queue handlers
* Moved here to avoid circular dependencies between handlers and queue * Properly located in handlers package instead of types
*/ */
import type { import type {
HandlerConfig, HandlerConfig,
HandlerConfigWithSchedule, HandlerConfigWithSchedule,
JobHandler, JobHandler,
ScheduledJob, ScheduledJob,
} from './handlers'; } from '@stock-bot/types';
import { getLogger } from '@stock-bot/logger'; import { getLogger } from '@stock-bot/logger';
class HandlerRegistry { class HandlerRegistry {
private readonly logger = getLogger('handler-registry'); private readonly logger = getLogger('handler-registry');
private handlers = new Map<string, HandlerConfig>(); private handlers = new Map<string, HandlerConfig>();
private handlerSchedules = new Map<string, ScheduledJob[]>(); private handlerSchedules = new Map<string, ScheduledJob[]>();
/** /**
* Register a handler with its operations (simple config) * Register a handler with its operations (simple config)
*/ */
register(handlerName: string, config: HandlerConfig): void { register(handlerName: string, config: HandlerConfig): void {
this.logger.info(`Registering handler: ${handlerName}`, { this.logger.info(`Registering handler: ${handlerName}`, {
operations: Object.keys(config), operations: Object.keys(config),
}); });
this.handlers.set(handlerName, config); this.handlers.set(handlerName, config);
} }
/** /**
* Register a handler with scheduled jobs (enhanced config) * Register a handler with scheduled jobs (enhanced config)
*/ */
registerWithSchedule(config: HandlerConfigWithSchedule): void { registerWithSchedule(config: HandlerConfigWithSchedule): void {
this.logger.info(`Registering handler with schedule: ${config.name}`, { this.logger.info(`Registering handler with schedule: ${config.name}`, {
operations: Object.keys(config.operations), operations: Object.keys(config.operations),
scheduledJobs: config.scheduledJobs?.length || 0, scheduledJobs: config.scheduledJobs?.length || 0,
}); });
this.handlers.set(config.name, config.operations); this.handlers.set(config.name, config.operations);
if (config.scheduledJobs && config.scheduledJobs.length > 0) { if (config.scheduledJobs && config.scheduledJobs.length > 0) {
this.handlerSchedules.set(config.name, config.scheduledJobs); this.handlerSchedules.set(config.name, config.scheduledJobs);
} }
} }
/** /**
* Get a specific handler's configuration * Get a specific handler's configuration
*/ */
getHandler(handlerName: string): HandlerConfig | undefined { getHandler(handlerName: string): HandlerConfig | undefined {
return this.handlers.get(handlerName); return this.handlers.get(handlerName);
} }
/** /**
* Get all registered handlers * Get all registered handlers
*/ */
getAllHandlers(): Map<string, HandlerConfig> { getAllHandlers(): Map<string, HandlerConfig> {
return new Map(this.handlers); return new Map(this.handlers);
} }
/** /**
* Get scheduled jobs for a handler * Get scheduled jobs for a handler
*/ */
getScheduledJobs(handlerName: string): ScheduledJob[] { getScheduledJobs(handlerName: string): ScheduledJob[] {
return this.handlerSchedules.get(handlerName) || []; return this.handlerSchedules.get(handlerName) || [];
} }
/** /**
* Get all handlers with their scheduled jobs * Get all handlers with their scheduled jobs
*/ */
getAllHandlersWithSchedule(): Map< getAllHandlersWithSchedule(): Map<
string, string,
{ operations: HandlerConfig; scheduledJobs: ScheduledJob[] } { operations: HandlerConfig; scheduledJobs: ScheduledJob[] }
> { > {
const result = new Map<string, { operations: HandlerConfig; scheduledJobs: ScheduledJob[] }>(); const result = new Map<string, { operations: HandlerConfig; scheduledJobs: ScheduledJob[] }>();
for (const [name, operations] of this.handlers) { for (const [name, operations] of this.handlers) {
result.set(name, { result.set(name, {
operations, operations,
scheduledJobs: this.handlerSchedules.get(name) || [], scheduledJobs: this.handlerSchedules.get(name) || [],
}); });
} }
return result; return result;
} }
/** /**
* Get a specific operation from a handler * Get a specific operation from a handler
*/ */
getOperation(handlerName: string, operationName: string): JobHandler | undefined { getOperation(handlerName: string, operationName: string): JobHandler | undefined {
const handler = this.handlers.get(handlerName); const handler = this.handlers.get(handlerName);
if (!handler) { if (!handler) {
return undefined; return undefined;
} }
return handler[operationName]; return handler[operationName];
} }
/** /**
* Check if a handler is registered * Check if a handler is registered
*/ */
hasHandler(handlerName: string): boolean { hasHandler(handlerName: string): boolean {
return this.handlers.has(handlerName); return this.handlers.has(handlerName);
} }
/** /**
* Get list of all registered handler names * Get list of all registered handler names
*/ */
getHandlerNames(): string[] { getHandlerNames(): string[] {
return Array.from(this.handlers.keys()); return Array.from(this.handlers.keys());
} }
/** /**
* Clear all registrations (useful for testing) * Get registry statistics
*/ */
clear(): void { getStats(): { handlers: number; operations: number; scheduledJobs: number } {
this.handlers.clear(); let operationCount = 0;
this.handlerSchedules.clear(); let scheduledJobCount = 0;
}
} for (const [_, config] of this.handlers) {
operationCount += Object.keys(config).length;
// Export singleton instance }
export const handlerRegistry = new HandlerRegistry();
for (const [_, jobs] of this.handlerSchedules) {
scheduledJobCount += jobs.length;
}
return {
handlers: this.handlers.size,
operations: operationCount,
scheduledJobs: scheduledJobCount,
};
}
/**
* Clear all registrations (useful for testing)
*/
clear(): void {
this.handlers.clear();
this.handlerSchedules.clear();
}
}
// Export singleton instance
export const handlerRegistry = new HandlerRegistry();

View file

@ -10,5 +10,3 @@ export type {
ScheduledJob, ScheduledJob,
TypedJobHandler, TypedJobHandler,
} from '@stock-bot/types'; } from '@stock-bot/types';
export { createJobHandler } from '@stock-bot/types';

View file

@ -0,0 +1,16 @@
/**
* Utility for creating typed job handlers
*/
import type { JobHandler, TypedJobHandler } from '@stock-bot/types';
/**
* Create a typed job handler with validation
*/
export function createJobHandler<TPayload = unknown, TResult = unknown>(
handler: TypedJobHandler<TPayload, TResult>
): JobHandler<unknown, TResult> {
return async (payload: unknown): Promise<TResult> => {
return handler(payload as TPayload);
};
}

View file

@ -71,13 +71,3 @@ export interface OperationMetadata {
validation?: (input: unknown) => boolean; validation?: (input: unknown) => boolean;
} }
/**
* Create a typed job handler with validation
*/
export function createJobHandler<TPayload = unknown, TResult = unknown>(
handler: TypedJobHandler<TPayload, TResult>
): JobHandler<unknown, TResult> {
return async (payload: unknown): Promise<TResult> => {
return handler(payload as TPayload);
};
}

View file

@ -60,7 +60,7 @@ export type {
ScheduledJob, ScheduledJob,
TypedJobHandler, TypedJobHandler,
} from './handlers'; } from './handlers';
export { createJobHandler } from './handlers';
// Export handler registry
export { handlerRegistry } from './handler-registry'; // Export service container types
export type { IServiceContainer } from './service-container';

View file

@ -1,28 +1,26 @@
/** /**
* Universal Service Container for Handlers * Universal Service Container for Handlers
* Simple, comprehensive container with all services available * Simple, comprehensive container with all services available
*/ */
import type { ProxyManager } from '@stock-bot/proxy'; /**
* Universal service container with all common services
/** * Designed to work across different service contexts (data-ingestion, processing, etc.)
* Universal service container with all common services */
* Designed to work across different service contexts (data-ingestion, processing, etc.) export interface IServiceContainer {
*/ // Core infrastructure
export interface IServiceContainer { readonly logger: any; // Logger instance
// Core infrastructure readonly cache?: any; // Cache provider (Redis/Dragonfly) - optional
readonly logger: any; // Logger instance readonly globalCache?: any; // Global cache provider (shared across services) - optional
readonly cache?: any; // Cache provider (Redis/Dragonfly) - optional readonly queue?: any; // Queue manager (BullMQ) - optional
readonly globalCache?: any; // Global cache provider (shared across services) - optional readonly proxy?: any; // Proxy manager service - optional (depends on cache)
readonly queue?: any; // Queue manager (BullMQ) - optional readonly browser?: any; // Browser automation (Playwright)
readonly proxy?: ProxyManager; // Proxy manager service - optional (depends on cache)
readonly browser?: any; // Browser automation (Playwright) // Database clients - all optional to support selective enabling
readonly mongodb?: any; // MongoDB client
// Database clients - all optional to support selective enabling readonly postgres?: any; // PostgreSQL client
readonly mongodb?: any; // MongoDB client readonly questdb?: any; // QuestDB client (time-series)
readonly postgres?: any; // PostgreSQL client
readonly questdb?: any; // QuestDB client (time-series) // Optional extensions for future use
readonly custom?: Record<string, any>;
// Optional extensions for future use }
readonly custom?: Record<string, any>;
}

View file

@ -15,7 +15,8 @@
"rate-limiter-flexible": "^3.0.0", "rate-limiter-flexible": "^3.0.0",
"@stock-bot/cache": "*", "@stock-bot/cache": "*",
"@stock-bot/logger": "*", "@stock-bot/logger": "*",
"@stock-bot/types": "*" "@stock-bot/types": "*",
"@stock-bot/handlers": "*"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.3.0", "typescript": "^5.3.0",

View file

@ -2,7 +2,6 @@
export { Queue, type QueueWorkerConfig } from './queue'; export { Queue, type QueueWorkerConfig } from './queue';
export { QueueManager } from './queue-manager'; export { QueueManager } from './queue-manager';
export { SmartQueueManager } from './smart-queue-manager'; export { SmartQueueManager } from './smart-queue-manager';
export { createJobHandler } from './types';
export { ServiceCache, createServiceCache } from './service-cache'; export { ServiceCache, createServiceCache } from './service-cache';
export { export {
SERVICE_REGISTRY, SERVICE_REGISTRY,
@ -12,8 +11,8 @@ export {
parseQueueName parseQueueName
} from './service-registry'; } from './service-registry';
// Re-export handler registry from types package // Re-export handler registry and utilities from handlers package
export { handlerRegistry } from '@stock-bot/types'; export { handlerRegistry, createJobHandler } from '@stock-bot/handlers';
// Batch processing // Batch processing
export { processBatchJob, processItems } from './batch-processor'; export { processBatchJob, processItems } from './batch-processor';

View file

@ -1,5 +1,5 @@
import { Queue as BullQueue, QueueEvents, Worker, type Job } from 'bullmq'; import { Queue as BullQueue, QueueEvents, Worker, type Job } from 'bullmq';
import { handlerRegistry } from '@stock-bot/types'; import { handlerRegistry } from '@stock-bot/handlers';
import type { JobData, JobOptions, QueueStats, RedisConfig } from './types'; import type { JobData, JobOptions, QueueStats, RedisConfig } from './types';
import { getRedisConnection } from './utils'; import { getRedisConnection } from './utils';

View file

@ -1,5 +1,5 @@
import { Queue as BullQueue, type Job } from 'bullmq'; import { Queue as BullQueue, type Job } from 'bullmq';
import { handlerRegistry } from '@stock-bot/types'; import { handlerRegistry } from '@stock-bot/handlers';
import { getLogger, type Logger } from '@stock-bot/logger'; import { getLogger, type Logger } from '@stock-bot/logger';
import { QueueManager } from './queue-manager'; import { QueueManager } from './queue-manager';
import { Queue } from './queue'; import { Queue } from './queue';

View file

@ -107,7 +107,6 @@ export interface QueueConfig extends QueueManagerConfig {
enableMetrics?: boolean; enableMetrics?: boolean;
} }
export { createJobHandler } from '@stock-bot/types';
export interface BatchJobData { export interface BatchJobData {
payloadKey: string; payloadKey: string;

View file

@ -42,22 +42,21 @@ libs=(
"data/postgres" # PostgreSQL client - depends on core libs "data/postgres" # PostgreSQL client - depends on core libs
"data/questdb" # QuestDB client - depends on core libs "data/questdb" # QuestDB client - depends on core libs
# Core handlers - must be built before services that depend on it
"core/handlers" # Handlers - depends on core libs
# Service libraries # Service libraries
"services/event-bus" # Event bus - depends on core libs "services/event-bus" # Event bus - depends on core libs
"services/shutdown" # Shutdown - depends on core libs "services/shutdown" # Shutdown - depends on core libs
"services/browser" # Browser - depends on core libs "services/browser" # Browser - depends on core libs
"services/queue" # Queue - depends on core libs and cache "services/queue" # Queue - depends on core libs, cache, and handlers
"services/proxy" # Proxy manager - depends on core libs and cache "services/proxy" # Proxy manager - depends on core libs and cache
# Utils # Utils
"utils" # Utilities - depends on many libs "utils" # Utilities - depends on many libs
# DI - dependency injection library # DI - dependency injection library
"core/di" # Dependency injection - depends on data and service libs "core/di" # Dependency injection - depends on data, service libs, and handlers
"core/handlers" # Handlers - depends on core libs and utils
# Note: core/handlers is not included in lib build chain since no libs depend on it
# It's built separately when needed by applications
) )
# Build each library in order # Build each library in order