cleaner dev experience refactor

This commit is contained in:
Boki 2025-06-22 07:31:00 -04:00
parent 8b17f98845
commit 742e590382
7 changed files with 407 additions and 17 deletions

View file

@ -0,0 +1,94 @@
/**
* Example Handler - Demonstrates ergonomic handler patterns
* Shows inline operations, service helpers, and scheduled operations
*/
import {
BaseHandler,
Handler,
Operation,
ScheduledOperation,
type ExecutionContext,
type IServiceContainer
} from '@stock-bot/handlers';
@Handler('example')
export class ExampleHandler extends BaseHandler {
constructor(services: IServiceContainer) {
super(services);
}
/**
* Simple inline operation - no separate action file needed
*/
@Operation('get-stats')
async getStats(): Promise<{ total: number; active: number; cached: boolean }> {
// Use collection helper for cleaner MongoDB access
const total = await this.collection('items').countDocuments();
const active = await this.collection('items').countDocuments({ status: 'active' });
// Use cache helpers with automatic prefixing
const cached = await this.cacheGet<number>('last-total');
await this.cacheSet('last-total', total, 300); // 5 minutes
// Use log helper with automatic handler context
this.log('info', 'Stats retrieved', { total, active });
return { total, active, cached: cached !== null };
}
/**
* Scheduled operation using combined decorator
*/
@ScheduledOperation('cleanup-old-items', '0 2 * * *', {
priority: 5,
description: 'Clean up items older than 30 days'
})
async cleanupOldItems(): Promise<{ deleted: number }> {
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const result = await this.collection('items').deleteMany({
createdAt: { $lt: thirtyDaysAgo }
});
this.log('info', 'Cleanup completed', { deleted: result.deletedCount });
// Schedule a follow-up task
await this.scheduleIn('generate-report', { type: 'cleanup' }, 60); // 1 minute
return { deleted: result.deletedCount };
}
/**
* Operation that uses proxy service
*/
@Operation('fetch-external-data')
async fetchExternalData(input: { url: string }): Promise<{ data: any }> {
const proxyUrl = this.proxy.getProxy();
if (!proxyUrl) {
throw new Error('No proxy available');
}
// Use HTTP client with proxy
const response = await this.http.get(input.url, {
proxy: proxyUrl,
timeout: 10000
});
// Cache the result
await this.cacheSet(`external:${input.url}`, response.data, 3600);
return { data: response.data };
}
/**
* Complex operation that still uses action file
*/
@Operation('process-batch')
async processBatch(input: any, context: ExecutionContext): Promise<unknown> {
// For complex operations, still use action files
const { processBatch } = await import('./actions/batch.action');
return processBatch(this, input);
}
}

View file

@ -1,29 +1,74 @@
/**
* Handler auto-registration
* Import all handlers here to trigger auto-registration
* Automatically discovers and registers all handlers
*/
import type { IDataIngestionServices } from '@stock-bot/di';
import { createServiceAdapter } from '@stock-bot/di';
import { QMHandler } from './qm/qm.handler';
import { WebShareHandler } from './webshare/webshare.handler';
import { autoRegisterHandlers } from '@stock-bot/handlers';
import { getLogger } from '@stock-bot/logger';
import { join } from 'path';
// Import handlers for bundling (ensures they're included in the build)
import './qm/qm.handler';
import './webshare/webshare.handler';
// Add more handler imports as needed
const logger = getLogger('handler-init');
/**
* Initialize and register all handlers
* Initialize and register all handlers automatically
*/
export function initializeAllHandlers(services: IDataIngestionServices): void {
export async function initializeAllHandlers(services: IDataIngestionServices): Promise<void> {
// Create generic service container adapter
const serviceContainer = createServiceAdapter(services);
// QM Handler
const qmHandler = new QMHandler(serviceContainer);
qmHandler.register();
try {
// Auto-register all handlers in this directory
const result = await autoRegisterHandlers(
__dirname,
serviceContainer,
{
pattern: '.handler.',
exclude: ['test', 'spec'],
dryRun: false
}
);
logger.info('Handler auto-registration complete', {
registered: result.registered,
failed: result.failed
});
if (result.failed.length > 0) {
logger.error('Some handlers failed to register', { failed: result.failed });
}
} catch (error) {
logger.error('Handler auto-registration failed', { error });
// Fall back to manual registration
await manualHandlerRegistration(serviceContainer);
}
}
/**
* Manual fallback registration
*/
async function manualHandlerRegistration(serviceContainer: any): Promise<void> {
logger.warn('Falling back to manual handler registration');
// WebShare Handler
const webShareHandler = new WebShareHandler(serviceContainer);
webShareHandler.register();
// TODO: Add other handlers here as they're converted
// const ibHandler = new IBHandler(serviceContainer);
// ibHandler.register();
try {
// Import and register handlers manually
const { QMHandler } = await import('./qm/qm.handler');
const qmHandler = new QMHandler(serviceContainer);
qmHandler.register();
const { WebShareHandler } = await import('./webshare/webshare.handler');
const webShareHandler = new WebShareHandler(serviceContainer);
webShareHandler.register();
logger.info('Manual handler registration complete');
} catch (error) {
logger.error('Manual handler registration failed', { error });
throw error;
}
}

View file

@ -85,7 +85,7 @@ async function initializeServices() {
logger.debug('Initializing data handlers with new DI pattern...');
// Auto-register all handlers
initializeAllHandlers(services);
await initializeAllHandlers(services);
logger.info('Data handlers initialized with new DI pattern');