added a smart queue manager and moved proxy logic to proxy manager to make handler just schedule a call to it

This commit is contained in:
Boki 2025-06-23 10:45:06 -04:00
parent da1c52a841
commit e7c0fe2798
19 changed files with 903 additions and 231 deletions

View file

@ -1,25 +1,28 @@
import { z } from 'zod';
import { redisConfigSchema } from './redis.schema';
import { mongodbConfigSchema } from './mongodb.schema';
import { postgresConfigSchema } from './postgres.schema';
import { questdbConfigSchema } from './questdb.schema';
import { proxyConfigSchema, browserConfigSchema, queueConfigSchema } from './service.schema';
export const appConfigSchema = z.object({
redis: redisConfigSchema,
mongodb: mongodbConfigSchema,
postgres: postgresConfigSchema,
questdb: questdbConfigSchema.optional(),
proxy: proxyConfigSchema.optional(),
browser: browserConfigSchema.optional(),
queue: queueConfigSchema.optional(),
});
export type AppConfig = z.infer<typeof appConfigSchema>;
// Re-export individual schemas and types
export * from './redis.schema';
export * from './mongodb.schema';
export * from './postgres.schema';
export * from './questdb.schema';
import { z } from 'zod';
import { redisConfigSchema } from './redis.schema';
import { mongodbConfigSchema } from './mongodb.schema';
import { postgresConfigSchema } from './postgres.schema';
import { questdbConfigSchema } from './questdb.schema';
import { proxyConfigSchema, browserConfigSchema, queueConfigSchema } from './service.schema';
export const appConfigSchema = z.object({
redis: redisConfigSchema,
mongodb: mongodbConfigSchema,
postgres: postgresConfigSchema,
questdb: questdbConfigSchema.optional(),
proxy: proxyConfigSchema.optional(),
browser: browserConfigSchema.optional(),
queue: queueConfigSchema.optional(),
service: z.object({
name: z.string(),
}).optional(),
});
export type AppConfig = z.infer<typeof appConfigSchema>;
// Re-export individual schemas and types
export * from './redis.schema';
export * from './mongodb.schema';
export * from './postgres.schema';
export * from './questdb.schema';
export * from './service.schema';

View file

@ -1,33 +1,38 @@
import { z } from 'zod';
export const proxyConfigSchema = z.object({
cachePrefix: z.string().optional().default('proxy:'),
ttl: z.number().optional().default(3600),
});
export const browserConfigSchema = z.object({
headless: z.boolean().optional().default(true),
timeout: z.number().optional().default(30000),
});
export const queueConfigSchema = z.object({
enabled: z.boolean().optional().default(true),
workers: z.number().optional().default(1),
concurrency: z.number().optional().default(1),
enableScheduledJobs: z.boolean().optional().default(true),
delayWorkerStart: z.boolean().optional().default(false),
defaultJobOptions: z.object({
attempts: z.number().default(3),
backoff: z.object({
type: z.enum(['exponential', 'fixed']).default('exponential'),
delay: z.number().default(1000),
}).default({}),
removeOnComplete: z.number().default(100),
removeOnFail: z.number().default(50),
timeout: z.number().optional(),
}).optional().default({}),
});
export type ProxyConfig = z.infer<typeof proxyConfigSchema>;
export type BrowserConfig = z.infer<typeof browserConfigSchema>;
import { z } from 'zod';
export const proxyConfigSchema = z.object({
enabled: z.boolean().default(false),
cachePrefix: z.string().optional().default('proxy:'),
ttl: z.number().optional().default(3600),
webshare: z.object({
apiKey: z.string(),
apiUrl: z.string().default('https://proxy.webshare.io/api/v2/'),
}).optional(),
});
export const browserConfigSchema = z.object({
headless: z.boolean().optional().default(true),
timeout: z.number().optional().default(30000),
});
export const queueConfigSchema = z.object({
enabled: z.boolean().optional().default(true),
workers: z.number().optional().default(1),
concurrency: z.number().optional().default(1),
enableScheduledJobs: z.boolean().optional().default(true),
delayWorkerStart: z.boolean().optional().default(false),
defaultJobOptions: z.object({
attempts: z.number().default(3),
backoff: z.object({
type: z.enum(['exponential', 'fixed']).default('exponential'),
delay: z.number().default(1000),
}).default({}),
removeOnComplete: z.number().default(100),
removeOnFail: z.number().default(50),
timeout: z.number().optional(),
}).optional().default({}),
});
export type ProxyConfig = z.infer<typeof proxyConfigSchema>;
export type BrowserConfig = z.infer<typeof browserConfigSchema>;
export type QueueConfig = z.infer<typeof queueConfigSchema>;

View file

@ -1,4 +1,4 @@
import { createContainer, InjectionMode, type AwilixContainer } from 'awilix';
import { createContainer, InjectionMode, asFunction, type AwilixContainer } from 'awilix';
import type { AppConfig as StockBotAppConfig } from '@stock-bot/config';
import { appConfigSchema, type AppConfig } from '../config/schemas';
import {
@ -100,7 +100,7 @@ export class ServiceContainerBuilder {
influxPort: 9009,
database: 'questdb',
}) : undefined,
proxy: this.options.enableProxy ? (config.proxy || { cachePrefix: 'proxy:', ttl: 3600 }) : undefined,
proxy: this.options.enableProxy ? (config.proxy || { enabled: false, cachePrefix: 'proxy:', ttl: 3600 }) : undefined,
browser: this.options.enableBrowser ? (config.browser || { headless: true, timeout: 30000 }) : undefined,
queue: this.options.enableQueue ? (config.queue || {
enabled: true,
@ -127,11 +127,12 @@ export class ServiceContainerBuilder {
// Register service container aggregate
container.register({
serviceContainer: asFunction(({
config, logger, cache, proxyManager, browser,
config, logger, cache, globalCache, proxyManager, browser,
queueManager, mongoClient, postgresClient, questdbClient
}) => ({
logger,
cache,
globalCache,
proxy: proxyManager, // Map proxyManager to proxy
browser,
queue: queueManager, // Map queueManager to queue
@ -181,10 +182,14 @@ export class ServiceContainerBuilder {
} : undefined,
queue: stockBotConfig.queue,
browser: stockBotConfig.browser,
proxy: stockBotConfig.proxy,
proxy: stockBotConfig.proxy ? {
...{
enabled: false,
cachePrefix: 'proxy:',
ttl: 3600,
},
...stockBotConfig.proxy
} : undefined,
};
}
}
// Add missing import
import { asFunction } from 'awilix';
}

View file

@ -1,13 +1,13 @@
import type { Browser } from '@stock-bot/browser';
import type { CacheProvider } from '@stock-bot/cache';
import type { IServiceContainer } from '@stock-bot/handlers';
import type { Logger } from '@stock-bot/logger';
import type { AppConfig } from '../config/schemas';
import type { CacheProvider } from '@stock-bot/cache';
import type { ProxyManager } from '@stock-bot/proxy';
import type { Browser } from '@stock-bot/browser';
import type { QueueManager } from '@stock-bot/queue';
import type { MongoDBClient } from '@stock-bot/mongodb';
import type { PostgreSQLClient } from '@stock-bot/postgres';
import type { ProxyManager } from '@stock-bot/proxy';
import type { QuestDBClient } from '@stock-bot/questdb';
import type { SmartQueueManager } from '@stock-bot/queue';
import type { AppConfig } from '../config/schemas';
export interface ServiceDefinitions {
// Configuration
@ -16,9 +16,10 @@ export interface ServiceDefinitions {
// Core services
cache: CacheProvider | null;
globalCache: CacheProvider | null;
proxyManager: ProxyManager | null;
browser: Browser;
queueManager: QueueManager | null;
queueManager: SmartQueueManager | null;
// Database clients
mongoClient: MongoDBClient | null;

View file

@ -10,18 +10,34 @@ export function registerCacheServices(
if (config.redis.enabled) {
container.register({
cache: asFunction(() => {
return createCache({
redisConfig: {
host: config.redis.host,
port: config.redis.port,
password: config.redis.password,
},
const { createServiceCache } = require('@stock-bot/queue');
const serviceName = config.service?.name || 'unknown';
// Create service-specific cache that uses the service's Redis DB
return createServiceCache(serviceName, {
host: config.redis.host,
port: config.redis.port,
password: config.redis.password,
db: config.redis.db, // This will be overridden by ServiceCache
});
}).singleton(),
// Also provide global cache for shared data
globalCache: asFunction(() => {
const { createServiceCache } = require('@stock-bot/queue');
const serviceName = config.service?.name || 'unknown';
return createServiceCache(serviceName, {
host: config.redis.host,
port: config.redis.port,
password: config.redis.password,
}, { global: true });
}).singleton(),
});
} else {
container.register({
cache: asValue(null),
globalCache: asValue(null),
});
}
}

View file

@ -32,9 +32,13 @@ export function registerApplicationServices(
if (config.proxy && config.redis.enabled) {
container.register({
proxyManager: asFunction(({ cache, logger }) => {
if (!cache) {return null;}
if (!cache) return null;
const proxyCache = new NamespacedCache(cache, 'proxy');
return new ProxyManager(proxyCache, logger);
const proxyManager = new ProxyManager(proxyCache, config.proxy, logger);
// Note: Initialization will be handled by the lifecycle manager
return proxyManager;
}).singleton(),
});
} else {
@ -47,8 +51,9 @@ export function registerApplicationServices(
if (config.queue?.enabled && config.redis.enabled) {
container.register({
queueManager: asFunction(({ logger }) => {
const { QueueManager } = require('@stock-bot/queue');
const { SmartQueueManager } = require('@stock-bot/queue');
const queueConfig = {
serviceName: config.service?.name || 'unknown',
redis: {
host: config.redis.host,
port: config.redis.port,
@ -62,8 +67,9 @@ export function registerApplicationServices(
},
enableScheduledJobs: config.queue!.enableScheduledJobs ?? true,
delayWorkerStart: config.queue!.delayWorkerStart ?? false,
autoDiscoverHandlers: true,
};
return new QueueManager(queueConfig, logger);
return new SmartQueueManager(queueConfig, logger);
}).singleton(),
});
} else {

View file

@ -15,6 +15,7 @@ export class ServiceLifecycleManager {
{ name: 'mongoClient', key: 'mongoClient' as const },
{ name: 'postgresClient', key: 'postgresClient' as const },
{ name: 'questdbClient', key: 'questdbClient' as const },
{ name: 'proxyManager', key: 'proxyManager' as const },
{ name: 'queueManager', key: 'queueManager' as const },
];