Skill Tracer
Skills › gstack › browse › buffers.ts

buffers.ts

137 lines · browse/src/buffers.ts at 3f79d81

1/**
2 * Shared buffers and types — extracted to break circular dependency
3 * between server.ts and browser-manager.ts
4 *
5 * CircularBuffer<T>: O(1) insert ring buffer with fixed capacity.
6 *
7 * ┌───┬───┬───┬───┬───┬───┐
8 * │ 3 │ 4 │ 5 │ │ 1 │ 2 │ capacity=6, head=4, size=5
9 * └───┴───┴───┴───┴─▲─┴───┘
10 * │
11 * head (oldest entry)
12 *
13 * push() writes at (head+size) % capacity, O(1)
14 * toArray() returns entries in insertion order, O(n)
15 * totalAdded keeps incrementing past capacity (flush cursor)
16 */
18// ─── CircularBuffer ─────────────────────────────────────────
20export class CircularBuffer<T> {
21 private buffer: (T | undefined)[];
22 private head: number = 0;
23 private _size: number = 0;
24 private _totalAdded: number = 0;
25 readonly capacity: number;
27 constructor(capacity: number) {
28 this.capacity = capacity;
29 this.buffer = new Array(capacity);
30 }
32 push(entry: T): void {
33 const index = (this.head + this._size) % this.capacity;
34 this.buffer[index] = entry;
35 if (this._size < this.capacity) {
36 this._size++;
37 } else {
38 // Buffer full — advance head (overwrites oldest)
39 this.head = (this.head + 1) % this.capacity;
40 }
41 this._totalAdded++;
42 }
44 /** Return entries in insertion order (oldest first) */
45 toArray(): T[] {
46 const result: T[] = [];
47 for (let i = 0; i < this._size; i++) {
48 result.push(this.buffer[(this.head + i) % this.capacity] as T);
49 }
50 return result;
51 }
53 /** Return the last N entries (most recent first → reversed to oldest first) */
54 last(n: number): T[] {
55 const count = Math.min(n, this._size);
56 const result: T[] = [];
57 const start = (this.head + this._size - count) % this.capacity;
58 for (let i = 0; i < count; i++) {
59 result.push(this.buffer[(start + i) % this.capacity] as T);
60 }
61 return result;
62 }
64 get length(): number {
65 return this._size;
66 }
68 get totalAdded(): number {
69 return this._totalAdded;
70 }
72 clear(): void {
73 this.head = 0;
74 this._size = 0;
75 // Don't reset totalAdded — flush cursor depends on it
76 }
78 /** Get entry by index (0 = oldest) — used by network response matching */
79 get(index: number): T | undefined {
80 if (index < 0 || index >= this._size) return undefined;
81 return this.buffer[(this.head + index) % this.capacity];
82 }
84 /** Set entry by index (0 = oldest) — used by network response matching */
85 set(index: number, entry: T): void {
86 if (index < 0 || index >= this._size) return;
87 this.buffer[(this.head + index) % this.capacity] = entry;
88 }
91// ─── Entry Types ────────────────────────────────────────────
93export interface LogEntry {
94 timestamp: number;
95 level: string;
96 text: string;
99export interface NetworkEntry {
100 timestamp: number;
101 method: string;
102 url: string;
103 status?: number;
104 duration?: number;
105 size?: number;
108export interface DialogEntry {
109 timestamp: number;
110 type: string; // 'alert' | 'confirm' | 'prompt' | 'beforeunload'
111 message: string;
112 defaultValue?: string;
113 action: string; // 'accepted' | 'dismissed'
114 response?: string; // text provided for prompt
117// ─── Buffer Instances ───────────────────────────────────────
119const HIGH_WATER_MARK = 50_000;
121export const consoleBuffer = new CircularBuffer<LogEntry>(HIGH_WATER_MARK);
122export const networkBuffer = new CircularBuffer<NetworkEntry>(HIGH_WATER_MARK);
123export const dialogBuffer = new CircularBuffer<DialogEntry>(HIGH_WATER_MARK);
125// ─── Convenience add functions ──────────────────────────────
127export function addConsoleEntry(entry: LogEntry) {
128 consoleBuffer.push(entry);
131export function addNetworkEntry(entry: NetworkEntry) {
132 networkBuffer.push(entry);
135export function addDialogEntry(entry: DialogEntry) {
136 dialogBuffer.push(entry);