• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

agentic-dev-library / thumbcode / 21933729139

12 Feb 2026 04:36AM UTC coverage: 28.401% (+0.7%) from 27.702%
21933729139

Pull #116

github

web-flow
Merge b9d1b07d1 into c6c31bd07
Pull Request #116: refactor: decompose 9 monolith files into focused modules

406 of 2268 branches covered (17.9%)

Branch coverage included in aggregate %.

365 of 845 new or added lines in 22 files covered. (43.2%)

1 existing line in 1 file now uncovered.

1120 of 3105 relevant lines covered (36.07%)

7.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

35.71
/src/services/chat/StreamHandler.ts
1
/**
2
 * Stream Handler
3
 *
4
 * Manages event system, abort controllers, and streaming infrastructure
5
 * for the chat service.
6
 */
7

8
import type { MessageSender } from '@thumbcode/state';
9
import type { ChatEvent, ChatEventListener } from './types';
10

11
export class StreamHandler {
12
  private listeners: Set<ChatEventListener> = new Set();
1✔
13
  private abortControllers: Map<string, AbortController> = new Map();
1✔
14

15
  /**
16
   * Subscribe to chat events
17
   */
18
  onEvent(listener: ChatEventListener): () => void {
19
    this.listeners.add(listener);
3✔
20
    return () => this.listeners.delete(listener);
3✔
21
  }
22

23
  /**
24
   * Emit an event to all listeners
25
   */
26
  emit(event: ChatEvent): void {
27
    for (const listener of this.listeners) {
18✔
28
      try {
3✔
29
        listener(event);
3✔
30
      } catch (error) {
NEW
31
        console.error('[ChatService] Error in event listener:', error);
×
32
      }
33
    }
34
  }
35

36
  /**
37
   * Register an abort controller for a thread
38
   */
39
  registerAbort(threadId: string): AbortController {
NEW
40
    const controller = new AbortController();
×
NEW
41
    this.abortControllers.set(threadId, controller);
×
NEW
42
    return controller;
×
43
  }
44

45
  /**
46
   * Cancel an ongoing response
47
   */
48
  cancelResponse(threadId: string): void {
49
    const controller = this.abortControllers.get(threadId);
1✔
50
    if (controller) {
1!
NEW
51
      controller.abort();
×
NEW
52
      this.abortControllers.delete(threadId);
×
53
    }
54
  }
55

56
  /**
57
   * Clean up an abort controller after completion
58
   */
59
  cleanupAbort(threadId: string): void {
NEW
60
    this.abortControllers.delete(threadId);
×
61
  }
62

63
  /**
64
   * Emit typing start event
65
   */
66
  emitTypingStart(threadId: string, sender: MessageSender): void {
NEW
67
    this.emit({ type: 'typing_start', threadId, sender });
×
68
  }
69

70
  /**
71
   * Emit typing end event
72
   */
73
  emitTypingEnd(threadId: string, sender: MessageSender): void {
NEW
74
    this.emit({ type: 'typing_end', threadId, sender });
×
75
  }
76

77
  /**
78
   * Helper to delay with abort support
79
   */
80
  delay(ms: number, signal?: AbortSignal): Promise<void> {
NEW
81
    return new Promise((resolve, reject) => {
×
NEW
82
      const timeout = setTimeout(resolve, ms);
×
NEW
83
      if (signal) {
×
NEW
84
        signal.addEventListener('abort', () => {
×
NEW
85
          clearTimeout(timeout);
×
NEW
86
          reject(new DOMException('Aborted', 'AbortError'));
×
87
        });
88
      }
89
    });
90
  }
91
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc