import { logger } from '../util/logger.js'; import { sleep, randomDelay } from '../util/sleep.js'; import type { TradeExecutor } from './TradeExecutor.js'; import type { TradeInfo, Config } from '../types.js'; export class TradeQueue { private queue: TradeInfo[] = []; private processing = false; constructor( private executor: TradeExecutor, private config: Config, ) {} enqueue(trade: TradeInfo): void { // De-duplicate: skip if same item ID already queued const existingIds = new Set(this.queue.flatMap((t) => t.itemIds)); const newIds = trade.itemIds.filter((id) => !existingIds.has(id)); if (newIds.length === 0) { logger.info({ itemIds: trade.itemIds }, 'Skipping duplicate trade'); return; } const dedupedTrade = { ...trade, itemIds: newIds }; this.queue.push(dedupedTrade); logger.info( { itemIds: newIds, queueLength: this.queue.length }, 'Trade enqueued', ); this.processNext(); } get length(): number { return this.queue.length; } get isProcessing(): boolean { return this.processing; } private async processNext(): Promise { if (this.processing || this.queue.length === 0) return; this.processing = true; const trade = this.queue.shift()!; try { logger.info( { searchId: trade.searchId, itemIds: trade.itemIds }, 'Processing trade', ); const success = await this.executor.executeTrade(trade); if (success) { logger.info({ itemIds: trade.itemIds }, 'Trade completed successfully'); } else { logger.warn({ itemIds: trade.itemIds }, 'Trade failed'); } } catch (err) { logger.error({ err, itemIds: trade.itemIds }, 'Trade execution error'); } this.processing = false; // Delay between trades await randomDelay(this.config.betweenTradesDelayMs, this.config.betweenTradesDelayMs + 3000); this.processNext(); } }