feat: Implement Worker Service v2 with improved architecture
- Complete rewrite of the Worker Service following object-oriented principles. - Introduced a single long-lived database connection to reduce overhead. - Implemented event-driven queues to eliminate polling. - Added DRY utilities for pagination and settings management. - Reduced code size significantly from 1173 lines to approximately 600-700 lines. - Created various composed services including DatabaseManager, SessionManager, and SDKAgent. - Enhanced SSE broadcasting capabilities for real-time client updates. - Established a structured approach for session lifecycle management and event handling. - Introduced type safety with shared TypeScript interfaces for better maintainability.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* SSEBroadcaster: SSE client management
|
||||
*
|
||||
* Responsibility:
|
||||
* - Manage SSE client connections
|
||||
* - Broadcast events to all connected clients
|
||||
* - Handle disconnections gracefully
|
||||
* - Single-pass broadcast (no two-step cleanup)
|
||||
*/
|
||||
|
||||
import type { Response } from 'express';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import type { SSEEvent, SSEClient } from '../worker-types.js';
|
||||
|
||||
export class SSEBroadcaster {
|
||||
private sseClients: Set<SSEClient> = new Set();
|
||||
|
||||
/**
|
||||
* Add a new SSE client connection
|
||||
*/
|
||||
addClient(res: Response): void {
|
||||
this.sseClients.add(res);
|
||||
logger.debug('WORKER', 'Client connected', { total: this.sseClients.size });
|
||||
|
||||
// Setup cleanup on disconnect
|
||||
res.on('close', () => {
|
||||
this.removeClient(res);
|
||||
});
|
||||
|
||||
// Send initial event
|
||||
this.sendToClient(res, { type: 'connected', timestamp: Date.now() });
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a client connection
|
||||
*/
|
||||
removeClient(res: Response): void {
|
||||
this.sseClients.delete(res);
|
||||
logger.debug('WORKER', 'Client disconnected', { total: this.sseClients.size });
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast an event to all connected clients (single-pass)
|
||||
*/
|
||||
broadcast(event: SSEEvent): void {
|
||||
if (this.sseClients.size === 0) {
|
||||
return; // Short-circuit if no clients
|
||||
}
|
||||
|
||||
const eventWithTimestamp = { ...event, timestamp: Date.now() };
|
||||
const data = `data: ${JSON.stringify(eventWithTimestamp)}\n\n`;
|
||||
|
||||
// Single-pass write + cleanup
|
||||
for (const client of this.sseClients) {
|
||||
try {
|
||||
client.write(data);
|
||||
} catch (err) {
|
||||
// Remove failed client immediately
|
||||
this.sseClients.delete(client);
|
||||
logger.debug('WORKER', 'Client removed due to write error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of connected clients
|
||||
*/
|
||||
getClientCount(): number {
|
||||
return this.sseClients.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send event to a specific client
|
||||
*/
|
||||
private sendToClient(res: Response, event: SSEEvent): void {
|
||||
const data = `data: ${JSON.stringify(event)}\n\n`;
|
||||
try {
|
||||
res.write(data);
|
||||
} catch (err) {
|
||||
this.sseClients.delete(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user