huge refactor with a million of things to make the code much more managable and easier to create new services #3

Merged
boki merged 70 commits from di-refactor into master 2025-06-24 01:43:57 +00:00
6 changed files with 112 additions and 147 deletions
Showing only changes of commit 4096e91e67 - Show all commits

View file

@ -5,6 +5,7 @@
import type { IDataIngestionServices } from '@stock-bot/di'; import type { IDataIngestionServices } from '@stock-bot/di';
import { QMHandler } from './qm/qm.handler'; import { QMHandler } from './qm/qm.handler';
import { WebShareHandler } from './webshare/webshare.handler';
/** /**
* Initialize and register all handlers * Initialize and register all handlers
@ -14,10 +15,11 @@ export function initializeAllHandlers(services: IDataIngestionServices): void {
const qmHandler = new QMHandler(services); const qmHandler = new QMHandler(services);
qmHandler.register(); qmHandler.register();
// TODO: Add other handlers here as they're converted // WebShare Handler
// const webShareHandler = new WebShareHandler(services); const webShareHandler = new WebShareHandler(services);
// webShareHandler.register(); webShareHandler.register();
// TODO: Add other handlers here as they're converted
// const ibHandler = new IBHandler(services); // const ibHandler = new IBHandler(services);
// ibHandler.register(); // ibHandler.register();
} }

View file

@ -93,13 +93,21 @@ export class QMHandler extends BaseHandler {
immediately: true, immediately: true,
description: 'Comprehensive symbol search using QM API' description: 'Comprehensive symbol search using QM API'
}) })
async spiderSymbolSearch(payload: SymbolSpiderJob, context: ExecutionContext): Promise<unknown> { async spiderSymbolSearch(payload: SymbolSpiderJob | undefined, context: ExecutionContext): Promise<unknown> {
this.logger.info('Starting QM spider symbol search', { payload }); // Set default payload for scheduled runs
const jobPayload: SymbolSpiderJob = payload || {
prefix: null,
depth: 1,
source: 'qm',
maxDepth: 4
};
this.logger.info('Starting QM spider symbol search', { payload: jobPayload });
// Store spider job info in cache (temporary data) // Store spider job info in cache (temporary data)
const spiderJobId = `spider:qm:${Date.now()}:${Math.random().toString(36).substr(2, 9)}`; const spiderJobId = `spider:qm:${Date.now()}:${Math.random().toString(36).substr(2, 9)}`;
const spiderResult = { const spiderResult = {
payload, payload: jobPayload,
startTime: new Date().toISOString(), startTime: new Date().toISOString(),
status: 'started', status: 'started',
jobId: spiderJobId jobId: spiderJobId
@ -119,18 +127,4 @@ export class QMHandler extends BaseHandler {
}; };
} }
/**
* Provide payloads for scheduled jobs
*/
protected getScheduledJobPayload(operation: string): any {
if (operation === 'spiderSymbolSearch') {
return {
prefix: null,
depth: 1,
source: 'qm',
maxDepth: 4
};
}
return undefined;
}
} }

View file

@ -1,82 +1,63 @@
/** import type { IDataIngestionServices } from '@stock-bot/di';
* WebShare Provider for proxy management with scheduled updates
*/
import { getLogger } from '@stock-bot/logger';
import { import {
createJobHandler, BaseHandler,
handlerRegistry, Handler,
type HandlerConfigWithSchedule, Operation,
} from '@stock-bot/queue'; QueueSchedule,
type ExecutionContext
} from '@stock-bot/handlers';
import { updateProxies } from '@stock-bot/utils'; import { updateProxies } from '@stock-bot/utils';
import type { ServiceContainer } from '@stock-bot/di';
const logger = getLogger('webshare-provider'); @Handler('webshare')
export class WebShareHandler extends BaseHandler {
constructor(services: IDataIngestionServices) {
super(services);
}
// Initialize and register the WebShare provider @Operation('fetch-proxies')
export function initializeWebShareProvider(_container: ServiceContainer) { @QueueSchedule('0 */6 * * *', {
logger.debug('Registering WebShare provider with scheduled jobs...'); priority: 3,
immediately: true,
const webShareProviderConfig: HandlerConfigWithSchedule = { description: 'Fetch fresh proxies from WebShare API'
name: 'webshare', })
async fetchProxies(_input: unknown, _context: ExecutionContext): Promise<unknown> {
operations: { this.logger.info('Fetching proxies from WebShare API');
'fetch-proxies': createJobHandler(async () => {
logger.info('Fetching proxies from WebShare API'); try {
const { fetchWebShareProxies } = await import('./operations/fetch.operations'); const { fetchWebShareProxies } = await import('./operations/fetch.operations');
const proxies = await fetchWebShareProxies();
if (proxies.length > 0) {
// Update the centralized proxy manager
await updateProxies(proxies);
try { this.logger.info('Updated proxy manager with WebShare proxies', {
const proxies = await fetchWebShareProxies(); count: proxies.length,
workingCount: proxies.filter(p => p.isWorking !== false).length,
if (proxies.length > 0) { });
// Update the centralized proxy manager
await updateProxies(proxies); // Cache proxy stats for monitoring
await this.cache.set('webshare-proxy-count', proxies.length, 3600);
logger.info('Updated proxy manager with WebShare proxies', { await this.cache.set('webshare-working-count', proxies.filter(p => p.isWorking !== false).length, 3600);
count: proxies.length, await this.cache.set('last-webshare-fetch', new Date().toISOString(), 1800);
workingCount: proxies.filter(p => p.isWorking !== false).length,
}); return {
success: true,
return { proxiesUpdated: proxies.length,
success: true, workingProxies: proxies.filter(p => p.isWorking !== false).length,
proxiesUpdated: proxies.length, };
workingProxies: proxies.filter(p => p.isWorking !== false).length, } else {
}; this.logger.warn('No proxies fetched from WebShare API');
} else { return {
logger.warn('No proxies fetched from WebShare API'); success: false,
return { proxiesUpdated: 0,
success: false, error: 'No proxies returned from API',
proxiesUpdated: 0, };
error: 'No proxies returned from API', }
}; } catch (error) {
} this.logger.error('Failed to fetch and update proxies', { error });
} catch (error) { throw error;
logger.error('Failed to fetch and update proxies', { error }); }
return { }
success: false,
proxiesUpdated: 0,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}),
},
scheduledJobs: [
{
type: 'webshare-fetch',
operation: 'fetch-proxies',
cronPattern: '0 */6 * * *', // Every 6 hours
priority: 3,
description: 'Fetch fresh proxies from WebShare API',
immediately: true, // Run on startup
},
],
};
handlerRegistry.registerWithSchedule(webShareProviderConfig);
logger.debug('WebShare provider registered successfully');
} }
export const webShareProvider = {
initialize: (container: ServiceContainer) => initializeWebShareProvider(container),
};

View file

@ -1,4 +1,4 @@
// Modern TC39 Stage 3 decorators for handler registration // Bun-compatible decorators (hybrid approach)
/** /**
* Handler decorator - marks a class as a handler * Handler decorator - marks a class as a handler
@ -7,13 +7,12 @@
export function Handler(name: string) { export function Handler(name: string) {
return function <T extends { new (...args: any[]): {} }>( return function <T extends { new (...args: any[]): {} }>(
target: T, target: T,
context: ClassDecoratorContext _context?: any
) { ) {
// Store handler name on the constructor // Store handler name on the constructor
(target as any).__handlerName = name; (target as any).__handlerName = name;
(target as any).__needsAutoRegistration = true; (target as any).__needsAutoRegistration = true;
console.log('Handler decorator applied', { name, className: context.name });
return target; return target;
}; };
} }
@ -22,39 +21,24 @@ export function Handler(name: string) {
* Operation decorator - marks a method as an operation * Operation decorator - marks a method as an operation
* @param name Operation name * @param name Operation name
*/ */
export function Operation(name: string) { export function Operation(name: string): any {
return function ( return function (
_target: Function, target: any,
context: ClassMethodDecoratorContext methodName: string,
) { descriptor?: PropertyDescriptor
const methodName = String(context.name); ): any {
// Store metadata directly on the class constructor
const constructor = target.constructor;
console.log('Operation decorator applied', { if (!constructor.__operations) {
operationName: name, constructor.__operations = [];
methodName, }
contextName: context.name, constructor.__operations.push({
contextKind: context.kind name,
method: methodName,
}); });
// Use context.addInitializer to run code when the class is constructed return descriptor;
context.addInitializer(function(this: any) {
const constructor = this.constructor as any;
if (!constructor.__operations) {
constructor.__operations = [];
}
constructor.__operations.push({
name,
method: methodName,
});
console.log('Operation registered via initializer', {
name,
methodName,
className: constructor.name
});
});
// Don't return anything - just modify metadata
}; };
} }
@ -70,27 +54,25 @@ export function QueueSchedule(
immediately?: boolean; immediately?: boolean;
description?: string; description?: string;
} }
) { ): any {
return function ( return function (
_target: Function, target: any,
context: ClassMethodDecoratorContext methodName: string,
) { descriptor?: PropertyDescriptor
const methodName = String(context.name); ): any {
// Store metadata directly on the class constructor
const constructor = target.constructor;
// Use context.addInitializer to run code when the class is constructed if (!constructor.__schedules) {
context.addInitializer(function(this: any) { constructor.__schedules = [];
const constructor = this.constructor as any; }
if (!constructor.__schedules) { constructor.__schedules.push({
constructor.__schedules = []; operation: methodName,
} cronPattern,
constructor.__schedules.push({ ...options,
operation: methodName,
cronPattern,
...options,
});
}); });
// Don't return anything - just modify metadata return descriptor;
}; };
} }

View file

@ -6,10 +6,12 @@
"composite": true, "composite": true,
"incremental": true, "incremental": true,
"types": ["bun-types"], "types": ["bun-types"],
// Modern TC39 decorators configuration // Modern TC39 Stage 3 decorators (TypeScript 5+ default)
"experimentalDecorators": false, "experimentalDecorators": false,
"emitDecoratorMetadata": false, "emitDecoratorMetadata": true,
"useDefineForClassFields": true // Suppress decorator-related type checking issues due to Bun's hybrid implementation
"skipLibCheck": true,
"suppressImplicitAnyIndexErrors": true
}, },
"include": ["src/**/*"], "include": ["src/**/*"],

View file

@ -37,6 +37,10 @@
"disableReferencedProjectLoad": true, "disableReferencedProjectLoad": true,
"disableSourceOfProjectReferenceRedirect": false, "disableSourceOfProjectReferenceRedirect": false,
// Decorator support for Bun's hybrid implementation
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
// Paths and output // Paths and output
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {