fixed up qm and added types to BaseHandler for typesafety

This commit is contained in:
Boki 2025-06-28 21:49:33 -04:00
parent 87b1cad4f5
commit 2e86598262
20 changed files with 164 additions and 109 deletions

View file

@ -75,7 +75,7 @@ export async function getChannels(this: CeoHandler, payload: number | undefined)
this.logger.info(`Fetched CEO channels for page ${page}/${totalPages}`); this.logger.info(`Fetched CEO channels for page ${page}/${totalPages}`);
return { page, totalPages }; return { page, totalPages };
} catch (error) { } catch (error: any) {
this.logger.error(`Error fetching CEO channels for page ${page} with proxy ${proxy}:`, error); this.logger.error(`Error fetching CEO channels for page ${page} with proxy ${proxy}:`, error);
throw new Error(`Failed to fetch CEO channels: ${error.message}`); throw new Error(`Failed to fetch CEO channels: ${error.message}`);
} }

View file

@ -6,10 +6,11 @@ import {
ScheduledOperation, ScheduledOperation,
} from '@stock-bot/handlers'; } from '@stock-bot/handlers';
import { getChannels, getPosts, getShorts, updateUniqueSymbols } from './actions'; import { getChannels, getPosts, getShorts, updateUniqueSymbols } from './actions';
import type { DataIngestionServices } from '../../types';
@Handler('ceo') @Handler('ceo')
@Disabled() @Disabled()
export class CeoHandler extends BaseHandler { export class CeoHandler extends BaseHandler<DataIngestionServices> {
constructor(services: any) { constructor(services: any) {
super(services); // Handler name read from @Handler decorator super(services); // Handler name read from @Handler decorator
} }

View file

@ -1,4 +1,3 @@
import type { IServiceContainer } from '@stock-bot/handlers';
import { fetchExchanges } from './fetch-exchanges.action'; import { fetchExchanges } from './fetch-exchanges.action';
import { fetchSession } from './fetch-session.action'; import { fetchSession } from './fetch-session.action';
import { fetchSymbols } from './fetch-symbols.action'; import { fetchSymbols } from './fetch-symbols.action';

View file

@ -2,10 +2,11 @@
* QM Corporate Actions - Fetch and update dividends, splits, and earnings together * QM Corporate Actions - Fetch and update dividends, splits, and earnings together
*/ */
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers'; import type { ExecutionContext } from '@stock-bot/handlers';
import type { QMHandler } from '../qm.handler';
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config'; import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager';
import { QMOperationTracker } from '../shared/operation-tracker'; import { QMOperationTracker } from '../shared/operation-tracker';
import { QMSessionManager } from '../shared/session-manager';
// Cache tracker instance // Cache tracker instance
let operationTracker: QMOperationTracker | null = null; let operationTracker: QMOperationTracker | null = null;
@ -13,7 +14,7 @@ let operationTracker: QMOperationTracker | null = null;
/** /**
* Get or initialize the operation tracker * Get or initialize the operation tracker
*/ */
async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTracker> { async function getOperationTracker(handler: QMHandler): Promise<QMOperationTracker> {
if (!operationTracker) { if (!operationTracker) {
const { initializeQMOperations } = await import('../shared/operation-registry'); const { initializeQMOperations } = await import('../shared/operation-registry');
operationTracker = await initializeQMOperations(handler.mongodb, handler.logger); operationTracker = await initializeQMOperations(handler.mongodb, handler.logger);
@ -26,7 +27,7 @@ async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTra
* Single API call returns all three data types * Single API call returns all three data types
*/ */
export async function updateCorporateActions( export async function updateCorporateActions(
this: BaseHandler, this: QMHandler,
input: { input: {
symbol: string; symbol: string;
symbolId: number; symbolId: number;
@ -191,7 +192,7 @@ export async function updateCorporateActions(
* Schedule corporate actions updates for symbols that need refreshing * Schedule corporate actions updates for symbols that need refreshing
*/ */
export async function scheduleCorporateActionsUpdates( export async function scheduleCorporateActionsUpdates(
this: BaseHandler, this: QMHandler,
input: { input: {
limit?: number; limit?: number;
forceUpdate?: boolean; forceUpdate?: boolean;

View file

@ -2,10 +2,11 @@
* QM Filings Actions - Fetch and update SEC filings * QM Filings Actions - Fetch and update SEC filings
*/ */
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers'; import type { ExecutionContext } from '@stock-bot/handlers';
import type { QMHandler } from '../qm.handler';
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config'; import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager';
import { QMOperationTracker } from '../shared/operation-tracker'; import { QMOperationTracker } from '../shared/operation-tracker';
import { QMSessionManager } from '../shared/session-manager';
// Cache tracker instance // Cache tracker instance
let operationTracker: QMOperationTracker | null = null; let operationTracker: QMOperationTracker | null = null;
@ -13,7 +14,7 @@ let operationTracker: QMOperationTracker | null = null;
/** /**
* Get or initialize the operation tracker * Get or initialize the operation tracker
*/ */
async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTracker> { async function getOperationTracker(handler: QMHandler): Promise<QMOperationTracker> {
if (!operationTracker) { if (!operationTracker) {
const { initializeQMOperations } = await import('../shared/operation-registry'); const { initializeQMOperations } = await import('../shared/operation-registry');
operationTracker = await initializeQMOperations(handler.mongodb, handler.logger); operationTracker = await initializeQMOperations(handler.mongodb, handler.logger);
@ -25,7 +26,7 @@ async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTra
* Update filings for a single symbol * Update filings for a single symbol
*/ */
export async function updateFilings( export async function updateFilings(
this: BaseHandler, this: QMHandler,
input: { input: {
symbol: string; symbol: string;
symbolId: number; symbolId: number;
@ -160,7 +161,7 @@ export async function updateFilings(
* Schedule filings updates for symbols that need refreshing * Schedule filings updates for symbols that need refreshing
*/ */
export async function scheduleFilingsUpdates( export async function scheduleFilingsUpdates(
this: BaseHandler, this: QMHandler,
input: { input: {
limit?: number; limit?: number;
forceUpdate?: boolean; forceUpdate?: boolean;

View file

@ -2,10 +2,11 @@
* QM Financials Actions - Fetch and update financial statements * QM Financials Actions - Fetch and update financial statements
*/ */
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers'; import type { ExecutionContext } from '@stock-bot/handlers';
import type { QMHandler } from '../qm.handler';
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config'; import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager';
import { QMOperationTracker } from '../shared/operation-tracker'; import { QMOperationTracker } from '../shared/operation-tracker';
import { QMSessionManager } from '../shared/session-manager';
// Cache tracker instance // Cache tracker instance
let operationTracker: QMOperationTracker | null = null; let operationTracker: QMOperationTracker | null = null;
@ -13,7 +14,7 @@ let operationTracker: QMOperationTracker | null = null;
/** /**
* Get or initialize the operation tracker * Get or initialize the operation tracker
*/ */
async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTracker> { async function getOperationTracker(handler: QMHandler): Promise<QMOperationTracker> {
if (!operationTracker) { if (!operationTracker) {
const { initializeQMOperations } = await import('../shared/operation-registry'); const { initializeQMOperations } = await import('../shared/operation-registry');
operationTracker = await initializeQMOperations(handler.mongodb, handler.logger); operationTracker = await initializeQMOperations(handler.mongodb, handler.logger);
@ -25,7 +26,7 @@ async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTra
* Update financials for a single symbol * Update financials for a single symbol
*/ */
export async function updateFinancials( export async function updateFinancials(
this: BaseHandler, this: QMHandler,
input: { input: {
symbol: string; symbol: string;
symbolId: number; symbolId: number;
@ -136,7 +137,6 @@ export async function updateFinancials(
const tracker = await getOperationTracker(this); const tracker = await getOperationTracker(this);
await tracker.updateSymbolOperation(symbol, 'financials_update', { await tracker.updateSymbolOperation(symbol, 'financials_update', {
status: 'failure', status: 'failure',
lastRunAt: new Date()
}); });
return { return {
@ -151,7 +151,7 @@ export async function updateFinancials(
* Schedule financial updates for symbols that need refreshing * Schedule financial updates for symbols that need refreshing
*/ */
export async function scheduleFinancialsUpdates( export async function scheduleFinancialsUpdates(
this: BaseHandler, this: QMHandler,
input: { input: {
limit?: number; limit?: number;
forceUpdate?: boolean; forceUpdate?: boolean;

View file

@ -2,10 +2,11 @@
* QM Intraday Actions - Fetch and update intraday price bars * QM Intraday Actions - Fetch and update intraday price bars
*/ */
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers'; import type { ExecutionContext } from '@stock-bot/handlers';
import type { QMHandler } from '../qm.handler';
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config'; import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager';
import { QMOperationTracker } from '../shared/operation-tracker'; import { QMOperationTracker } from '../shared/operation-tracker';
import { QMSessionManager } from '../shared/session-manager';
// Cache tracker instance // Cache tracker instance
let operationTracker: QMOperationTracker | null = null; let operationTracker: QMOperationTracker | null = null;
@ -13,7 +14,7 @@ let operationTracker: QMOperationTracker | null = null;
/** /**
* Get or initialize the operation tracker * Get or initialize the operation tracker
*/ */
async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTracker> { async function getOperationTracker(handler: QMHandler): Promise<QMOperationTracker> {
if (!operationTracker) { if (!operationTracker) {
const { initializeQMOperations } = await import('../shared/operation-registry'); const { initializeQMOperations } = await import('../shared/operation-registry');
operationTracker = await initializeQMOperations(handler.mongodb, handler.logger); operationTracker = await initializeQMOperations(handler.mongodb, handler.logger);
@ -26,7 +27,7 @@ async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTra
* This handles both initial crawl and incremental updates * This handles both initial crawl and incremental updates
*/ */
export async function updateIntradayBars( export async function updateIntradayBars(
this: BaseHandler, this: QMHandler,
input: { input: {
symbol: string; symbol: string;
symbolId: number; symbolId: number;
@ -66,7 +67,7 @@ export async function updateIntradayBars(
webmasterId: '500', webmasterId: '500',
date: targetDate.toISOString().split('T')[0], date: targetDate.toISOString().split('T')[0],
interval: '1' // 1-minute bars interval: '1' // 1-minute bars
}); } as Record<string, string>);
// TODO: Update with correct intraday endpoint // TODO: Update with correct intraday endpoint
const apiUrl = `${QM_CONFIG.BASE_URL}/datatool/intraday.json?${searchParams.toString()}`; const apiUrl = `${QM_CONFIG.BASE_URL}/datatool/intraday.json?${searchParams.toString()}`;
@ -157,7 +158,7 @@ export async function updateIntradayBars(
* This handles both initial crawls and regular updates * This handles both initial crawls and regular updates
*/ */
export async function scheduleIntradayUpdates( export async function scheduleIntradayUpdates(
this: BaseHandler, this: QMHandler,
input: { input: {
limit?: number; limit?: number;
mode?: 'crawl' | 'update'; // crawl for historical, update for recent mode?: 'crawl' | 'update'; // crawl for historical, update for recent
@ -259,7 +260,6 @@ export async function scheduleIntradayUpdates(
crawlState: { crawlState: {
finished: false, finished: false,
oldestDateReached: new Date(startDate.getTime() - daysToFetch * 24 * 60 * 60 * 1000), oldestDateReached: new Date(startDate.getTime() - daysToFetch * 24 * 60 * 60 * 1000),
lastCrawlDirection: 'backward'
} }
}); });
} else { } else {

View file

@ -2,10 +2,11 @@
* QM Prices Actions - Fetch and update daily price data * QM Prices Actions - Fetch and update daily price data
*/ */
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers'; import type { ExecutionContext } from '@stock-bot/handlers';
import type { QMHandler } from '../qm.handler';
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config'; import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager';
import { QMOperationTracker } from '../shared/operation-tracker'; import { QMOperationTracker } from '../shared/operation-tracker';
import { QMSessionManager } from '../shared/session-manager';
// Cache tracker instance // Cache tracker instance
let operationTracker: QMOperationTracker | null = null; let operationTracker: QMOperationTracker | null = null;
@ -13,7 +14,7 @@ let operationTracker: QMOperationTracker | null = null;
/** /**
* Get or initialize the operation tracker * Get or initialize the operation tracker
*/ */
async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTracker> { async function getOperationTracker(handler: QMHandler): Promise<QMOperationTracker> {
if (!operationTracker) { if (!operationTracker) {
const { initializeQMOperations } = await import('../shared/operation-registry'); const { initializeQMOperations } = await import('../shared/operation-registry');
operationTracker = await initializeQMOperations(handler.mongodb, handler.logger); operationTracker = await initializeQMOperations(handler.mongodb, handler.logger);
@ -25,7 +26,7 @@ async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTra
* Update daily prices for a single symbol * Update daily prices for a single symbol
*/ */
export async function updatePrices( export async function updatePrices(
this: BaseHandler, this: QMHandler,
input: { input: {
symbol: string; symbol: string;
symbolId: number; symbolId: number;
@ -164,7 +165,7 @@ export async function updatePrices(
* Schedule price updates for symbols that need refreshing * Schedule price updates for symbols that need refreshing
*/ */
export async function schedulePriceUpdates( export async function schedulePriceUpdates(
this: BaseHandler, this: QMHandler,
input: { input: {
limit?: number; limit?: number;
forceUpdate?: boolean; forceUpdate?: boolean;

View file

@ -2,18 +2,18 @@
* QM Session Actions - Session management and creation * QM Session Actions - Session management and creation
*/ */
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers'; import type { ExecutionContext } from '@stock-bot/handlers';
import { BunRequestInit } from '@stock-bot/utils'; import type { QMHandler } from '../qm.handler';
import { getQmHeaders, QM_CONFIG, QM_SESSION_IDS, SESSION_CONFIG } from '../shared/config'; import { getQmHeaders, QM_CONFIG, QM_SESSION_IDS, SESSION_CONFIG } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager'; import { QMSessionManager } from '../shared/session-manager';
import { QMSession } from '../shared/types'; import { type QMSession } from '../shared/types';
/** /**
* Check existing sessions and queue creation jobs for needed sessions * Check existing sessions and queue creation jobs for needed sessions
* This is the main session management function that handles cleanup, maintenance, and initialization * This is the main session management function that handles cleanup, maintenance, and initialization
*/ */
export async function checkSessions( export async function checkSessions(
this: BaseHandler, this: QMHandler,
_input: unknown, _input: unknown,
_context: ExecutionContext _context: ExecutionContext
): Promise<{ ): Promise<{
@ -80,7 +80,7 @@ interface CreateSessionInput {
} }
export async function createSession( export async function createSession(
this: BaseHandler, this: QMHandler,
input: CreateSessionInput input: CreateSessionInput
): Promise<{ sessionId: string; status: string; sessionType: string, session?: QMSession }> { ): Promise<{ sessionId: string; status: string; sessionType: string, session?: QMSession }> {
const { sessionId, sessionType = 'LOOKUP' } = input || {}; const { sessionId, sessionType = 'LOOKUP' } = input || {};
@ -108,7 +108,7 @@ export async function createSession(
const sessionUrl = `${QM_CONFIG.BASE_URL}${QM_CONFIG.SESSION_PATH}/${sessionId}`; const sessionUrl = `${QM_CONFIG.BASE_URL}${QM_CONFIG.SESSION_PATH}/${sessionId}`;
// Build request options // Build request options
const sessionRequest: BunRequestInit = { const sessionRequest = {
proxy: proxyUrl || undefined, proxy: proxyUrl || undefined,
headers: getQmHeaders(), headers: getQmHeaders(),
}; };

View file

@ -2,10 +2,11 @@
* QM Symbol Info Actions - Fetch and update symbol metadata * QM Symbol Info Actions - Fetch and update symbol metadata
*/ */
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers'; import type { ExecutionContext } from '@stock-bot/handlers';
import type { QMHandler } from '../qm.handler';
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config'; import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager';
import { QMOperationTracker } from '../shared/operation-tracker'; import { QMOperationTracker } from '../shared/operation-tracker';
import { QMSessionManager } from '../shared/session-manager';
// Cache tracker instance // Cache tracker instance
let operationTracker: QMOperationTracker | null = null; let operationTracker: QMOperationTracker | null = null;
@ -13,7 +14,7 @@ let operationTracker: QMOperationTracker | null = null;
/** /**
* Get or initialize the operation tracker * Get or initialize the operation tracker
*/ */
async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTracker> { async function getOperationTracker(handler: QMHandler): Promise<QMOperationTracker> {
if (!operationTracker) { if (!operationTracker) {
const { initializeQMOperations } = await import('../shared/operation-registry'); const { initializeQMOperations } = await import('../shared/operation-registry');
operationTracker = await initializeQMOperations(handler.mongodb, handler.logger); operationTracker = await initializeQMOperations(handler.mongodb, handler.logger);
@ -26,7 +27,7 @@ async function getOperationTracker(handler: BaseHandler): Promise<QMOperationTra
* This is a simple API fetch operation - no tracking logic here * This is a simple API fetch operation - no tracking logic here
*/ */
export async function updateSymbolInfo( export async function updateSymbolInfo(
this: BaseHandler, this: QMHandler,
input: { input: {
symbol: string; symbol: string;
qmSearchCode: string; qmSearchCode: string;
@ -144,7 +145,7 @@ export async function updateSymbolInfo(
* This is the scheduled job that finds stale symbols and queues individual updates * This is the scheduled job that finds stale symbols and queues individual updates
*/ */
export async function scheduleSymbolInfoUpdates( export async function scheduleSymbolInfoUpdates(
this: BaseHandler, this: QMHandler,
input: { input: {
limit?: number; limit?: number;
forceUpdate?: boolean; forceUpdate?: boolean;

View file

@ -2,11 +2,12 @@
* QM Symbol Actions - Symbol search and spider operations * QM Symbol Actions - Symbol search and spider operations
*/ */
import type { BaseHandler, ExecutionContext } from '@stock-bot/handlers'; import type { ExecutionContext } from '@stock-bot/handlers';
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config'; import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
import { QMSessionManager } from '../shared/session-manager'; import { QMSessionManager } from '../shared/session-manager';
// import { QMOperationTracker } from '../shared/operation-tracker'; // import { QMOperationTracker } from '../shared/operation-tracker';
// import { initializeQMOperations } from '../shared/operation-registry'; // import { initializeQMOperations } from '../shared/operation-registry';
import type { QMHandler } from '../qm.handler';
import type { Exchange, SymbolSpiderJob } from '../shared/types'; import type { Exchange, SymbolSpiderJob } from '../shared/types';
/** /**
@ -15,7 +16,7 @@ import type { Exchange, SymbolSpiderJob } from '../shared/types';
* Each job searches its prefix and creates child jobs if needed * Each job searches its prefix and creates child jobs if needed
*/ */
export async function spiderSymbol( export async function spiderSymbol(
this: BaseHandler, this: QMHandler,
input: SymbolSpiderJob, input: SymbolSpiderJob,
_context: ExecutionContext _context: ExecutionContext
): Promise<{ ): Promise<{
@ -62,7 +63,7 @@ export async function spiderSymbol(
}; };
} }
await this.mongodb.batchUpsert('qmSymbols', symbols, ['qmSearchCode']); await this.mongodb?.batchUpsert('qmSymbols', symbols, ['qmSearchCode']);
this.logger.info(`Stored symbols from spider search ${prefix} - ${symbols.length}`, { this.logger.info(`Stored symbols from spider search ${prefix} - ${symbols.length}`, {
prefix, prefix,
@ -84,7 +85,7 @@ export async function spiderSymbol(
} }
if (exchanges.length > 0) { if (exchanges.length > 0) {
await this.mongodb.batchUpsert('qmExchanges', exchanges, ['exchange']); await this.mongodb?.batchUpsert('qmExchanges', exchanges, ['exchange']);
this.logger.debug('Stored exchanges from spider search', { this.logger.debug('Stored exchanges from spider search', {
count: exchanges.length count: exchanges.length
}); });
@ -136,7 +137,7 @@ export async function spiderSymbol(
* Search QM symbols API directly * Search QM symbols API directly
*/ */
export async function searchSymbols( export async function searchSymbols(
this: BaseHandler, this: QMHandler,
input: { query: string }, input: { query: string },
_context?: ExecutionContext _context?: ExecutionContext
): Promise<any[]> { ): Promise<any[]> {

View file

@ -4,6 +4,7 @@ import {
Operation, Operation,
ScheduledOperation, ScheduledOperation,
} from '@stock-bot/handlers'; } from '@stock-bot/handlers';
import type { DataIngestionServices } from '../../types';
import { import {
checkSessions, checkSessions,
createSession, createSession,
@ -25,7 +26,7 @@ import {
import { initializeQMOperations } from './shared/operation-registry'; import { initializeQMOperations } from './shared/operation-registry';
@Handler('qm') @Handler('qm')
export class QMHandler extends BaseHandler { export class QMHandler extends BaseHandler<DataIngestionServices> {
constructor(services: any) { constructor(services: any) {
super(services); // Handler name read from @Handler decorator super(services); // Handler name read from @Handler decorator
// Initialize operations after super() so services are available // Initialize operations after super() so services are available

View file

@ -3,7 +3,7 @@
* Supports dynamic operation registration with auto-indexing * Supports dynamic operation registration with auto-indexing
*/ */
import type { MongoDBClient, Logger } from '@stock-bot/types'; import type { Logger, MongoDBClient } from '@stock-bot/types';
import type { IntradayCrawlSymbol, QMOperationConfig } from './types'; import type { IntradayCrawlSymbol, QMOperationConfig } from './types';
export class QMOperationTracker { export class QMOperationTracker {
@ -118,7 +118,7 @@ export class QMOperationTracker {
await this.mongodb.updateOne(this.collectionName, { symbol }, update); await this.mongodb.updateOne(this.collectionName, { symbol }, update);
this.logger.trace('Updated symbol operation', { this.logger.debug('Updated symbol operation', {
symbol, symbol,
operation: operationName, operation: operationName,
status: data.status status: data.status

View file

@ -0,0 +1,25 @@
/**
* Data Ingestion Service Types
* Defines required services for data ingestion handlers
*/
import type { ServiceTypes } from '@stock-bot/types';
/**
* Services required by data ingestion handlers
* Makes specific services non-nullable since they're always enabled
*/
export type DataIngestionServices = ServiceTypes & {
// Always required services
logger: NonNullable<ServiceTypes['logger']>;
mongodb: NonNullable<ServiceTypes['mongodb']>;
cache: NonNullable<ServiceTypes['cache']>;
queue: NonNullable<ServiceTypes['queue']>;
queueManager: NonNullable<ServiceTypes['queueManager']>;
browser: NonNullable<ServiceTypes['browser']>;
proxy: NonNullable<ServiceTypes['proxy']>;
// Optional services remain optional
postgres: ServiceTypes['postgres'];
questdb: ServiceTypes['questdb'];
globalCache: ServiceTypes['globalCache'];
};

View file

@ -1,18 +1,18 @@
{ {
"extends": "../../tsconfig.app.json", "extends": "../../../tsconfig.app.json",
"references": [ "references": [
{ "path": "../../libs/core/types" }, { "path": "../../../libs/core/types" },
{ "path": "../../libs/core/config" }, { "path": "../../../libs/core/config" },
{ "path": "../../libs/core/logger" }, { "path": "../../../libs/core/logger" },
{ "path": "../../libs/core/di" }, { "path": "../../../libs/core/di" },
{ "path": "../../libs/core/handlers" }, { "path": "../../../libs/core/handlers" },
{ "path": "../../libs/data/cache" }, { "path": "../../../libs/core/cache" },
{ "path": "../../libs/data/mongodb" }, { "path": "../../../libs/data/mongodb" },
{ "path": "../../libs/data/postgres" }, { "path": "../../../libs/data/postgres" },
{ "path": "../../libs/data/questdb" }, { "path": "../../../libs/data/questdb" },
{ "path": "../../libs/services/queue" }, { "path": "../../../libs/core/queue" },
{ "path": "../../libs/services/shutdown" }, { "path": "../../../libs/core/shutdown" },
{ "path": "../../libs/utils" }, { "path": "../../../libs/utils" },
{ "path": "../config" } { "path": "../config" }
] ]
} }

View file

@ -1,14 +1,14 @@
{ {
"extends": "../../tsconfig.app.json", "extends": "../../../tsconfig.app.json",
"references": [ "references": [
{ "path": "../../libs/core/types" }, { "path": "../../../libs/core/types" },
{ "path": "../../libs/core/config" }, { "path": "../../../libs/core/config" },
{ "path": "../../libs/core/logger" }, { "path": "../../../libs/core/logger" },
{ "path": "../../libs/data/cache" }, { "path": "../../../libs/core/cache" },
{ "path": "../../libs/services/queue" }, { "path": "../../../libs/core/queue" },
{ "path": "../../libs/data/mongodb" }, { "path": "../../../libs/data/mongodb" },
{ "path": "../../libs/data/postgres" }, { "path": "../../../libs/data/postgres" },
{ "path": "../../libs/data/questdb" }, { "path": "../../../libs/data/questdb" },
{ "path": "../../libs/services/shutdown" } { "path": "../../../libs/core/shutdown" }
] ]
} }

View file

@ -0,0 +1,25 @@
/**
* Web API Service Types
* Defines required services for web API handlers
*/
import type { ServiceTypes } from '@stock-bot/types';
/**
* Services required by web API handlers
* Makes specific services non-nullable since they're always enabled
*/
export type WebApiServices = ServiceTypes & {
// Always required services
logger: NonNullable<ServiceTypes['logger']>;
mongodb: NonNullable<ServiceTypes['mongodb']>;
postgres: NonNullable<ServiceTypes['postgres']>;
cache: NonNullable<ServiceTypes['cache']>;
queue: NonNullable<ServiceTypes['queue']>;
queueManager: NonNullable<ServiceTypes['queueManager']>;
// Optional services remain optional
browser: ServiceTypes['browser'];
proxy: ServiceTypes['proxy'];
questdb: ServiceTypes['questdb'];
globalCache: ServiceTypes['globalCache'];
};

View file

@ -1,14 +1,14 @@
{ {
"extends": "../../tsconfig.app.json", "extends": "../../../tsconfig.app.json",
"references": [ "references": [
{ "path": "../../libs/core/types" }, { "path": "../../../libs/core/types" },
{ "path": "../../libs/core/config" }, { "path": "../../../libs/core/config" },
{ "path": "../../libs/core/logger" }, { "path": "../../../libs/core/logger" },
{ "path": "../../libs/data/cache" }, { "path": "../../../libs/core/cache" },
{ "path": "../../libs/services/queue" }, { "path": "../../../libs/core/queue" },
{ "path": "../../libs/data/mongodb" }, { "path": "../../../libs/data/mongodb" },
{ "path": "../../libs/data/postgres" }, { "path": "../../../libs/data/postgres" },
{ "path": "../../libs/data/questdb" }, { "path": "../../../libs/data/questdb" },
{ "path": "../../libs/services/shutdown" } { "path": "../../../libs/core/shutdown" }
] ]
} }

View file

@ -40,41 +40,41 @@ export interface JobScheduleOptions {
* Abstract base class for all handlers with improved DI * Abstract base class for all handlers with improved DI
* Provides common functionality and structure for queue/event operations * Provides common functionality and structure for queue/event operations
*/ */
export abstract class BaseHandler implements IHandler { export abstract class BaseHandler<TServices extends ServiceTypes = ServiceTypes> implements IHandler {
// Direct service properties - flattened for cleaner access with proper types // Direct service properties - flattened for cleaner access with proper types
readonly logger: ServiceTypes['logger']; readonly logger: TServices['logger'];
readonly cache: ServiceTypes['cache']; readonly cache: TServices['cache'];
readonly globalCache: ServiceTypes['globalCache']; readonly globalCache: TServices['globalCache'];
readonly queueManager: ServiceTypes['queueManager']; readonly queueManager: TServices['queueManager'];
readonly queue: ServiceTypes['queue']; // Specific queue for this handler readonly queue!: TServices['queue']; // Specific queue for this handler - initialized if queueManager exists
readonly proxy: ServiceTypes['proxy']; readonly proxy: TServices['proxy'];
readonly browser: ServiceTypes['browser']; readonly browser: TServices['browser'];
readonly mongodb: ServiceTypes['mongodb']; readonly mongodb: TServices['mongodb'];
readonly postgres: ServiceTypes['postgres']; readonly postgres: TServices['postgres'];
readonly questdb: ServiceTypes['questdb']; readonly questdb: TServices['questdb'];
private handlerName: string; private handlerName: string;
constructor(services: IServiceContainer, handlerName?: string) { constructor(services: TServices, handlerName?: string) {
// Read handler name from decorator first, then fallback to parameter or class name // Read handler name from decorator first, then fallback to parameter or class name
const constructor = this.constructor as any; const constructor = this.constructor as any;
this.handlerName = this.handlerName =
constructor.__handlerName || handlerName || this.constructor.name.toLowerCase(); constructor.__handlerName || handlerName || this.constructor.name.toLowerCase();
// Flatten all services onto the handler instance // Flatten all services onto the handler instance
this.logger = getLogger(this.constructor.name); this.logger = getLogger(this.constructor.name) as TServices['logger'];
this.cache = services.cache; this.cache = services.cache as TServices['cache'];
this.globalCache = services.globalCache; this.globalCache = services.globalCache as TServices['globalCache'];
this.queueManager = services.queueManager; this.queueManager = services.queueManager as TServices['queueManager'];
this.proxy = services.proxy; this.proxy = services.proxy as TServices['proxy'];
this.browser = services.browser; this.browser = services.browser as TServices['browser'];
this.mongodb = services.mongodb; this.mongodb = services.mongodb as TServices['mongodb'];
this.postgres = services.postgres; this.postgres = services.postgres as TServices['postgres'];
this.questdb = services.questdb; this.questdb = services.questdb as TServices['questdb'];
// Get the specific queue for this handler // Get the specific queue for this handler
if (this.queueManager) { if (this.queueManager) {
this.queue = this.queueManager.getQueue(this.handlerName); this.queue = this.queueManager.getQueue(this.handlerName) as TServices['queue'];
} }
} }

View file

@ -6,12 +6,11 @@
"composite": true, "composite": true,
"incremental": true, "incremental": true,
"types": ["bun-types"], "types": ["bun-types"],
// Modern TC39 Stage 3 decorators (TypeScript 5+ default) // Use legacy decorators for compatibility with the handler system
"experimentalDecorators": false, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
// Suppress decorator-related type checking issues due to Bun's hybrid implementation // Suppress decorator-related type checking issues due to Bun's hybrid implementation
"skipLibCheck": true, "skipLibCheck": true
"suppressImplicitAnyIndexErrors": true
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"] "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]