Skip to content

Commit 012e8eb

Browse files
committed
refactor: change architecture
1 parent 44c061d commit 012e8eb

25 files changed

+2429
-1056
lines changed

packages/plugin-bridge/src/v2/__tests__/client.test.ts

Lines changed: 312 additions & 355 deletions
Large diffs are not rendered by default.

packages/plugin-bridge/src/v2/__tests__/connection.test.ts

Lines changed: 441 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
import { describe, it, expect, beforeEach, vi } from 'vitest';
2+
import { createTypedMessageBuffer } from '../client/typed-message-buffer.js';
3+
import { UserMessage } from '../connection/types.js';
4+
5+
const createMessage = (type: string, data: unknown): UserMessage => ({
6+
type,
7+
data,
8+
timestamp: Date.now(),
9+
});
10+
11+
describe('TypedMessageBuffer', () => {
12+
describe('Handler registration', () => {
13+
it('should deliver messages immediately when handler exists', () => {
14+
const buffer = createTypedMessageBuffer();
15+
const received: UserMessage[] = [];
16+
17+
buffer.onMessage('test', (msg) => received.push(msg));
18+
buffer.handleMessage(createMessage('test', { value: 1 }));
19+
20+
expect(received).toHaveLength(1);
21+
expect(received[0].data).toEqual({ value: 1 });
22+
23+
buffer.close();
24+
});
25+
26+
it('should buffer messages when no handler exists', () => {
27+
const buffer = createTypedMessageBuffer();
28+
29+
buffer.handleMessage(createMessage('test', { value: 1 }));
30+
buffer.handleMessage(createMessage('test', { value: 2 }));
31+
32+
expect(buffer.getBufferedCount('test')).toBe(2);
33+
expect(buffer.getTotalBufferedCount()).toBe(2);
34+
35+
buffer.close();
36+
});
37+
38+
it('should replay buffered messages when first handler is registered', async () => {
39+
const buffer = createTypedMessageBuffer();
40+
const received: UserMessage[] = [];
41+
42+
// Buffer some messages
43+
buffer.handleMessage(createMessage('test', { value: 1 }));
44+
buffer.handleMessage(createMessage('test', { value: 2 }));
45+
buffer.handleMessage(createMessage('test', { value: 3 }));
46+
47+
expect(buffer.getBufferedCount('test')).toBe(3);
48+
49+
// Register handler - should trigger replay
50+
buffer.onMessage('test', (msg) => received.push(msg));
51+
52+
// Wait for replay (happens on next tick)
53+
await new Promise((resolve) => setTimeout(resolve, 10));
54+
55+
expect(received).toHaveLength(3);
56+
expect(received[0].data).toEqual({ value: 1 });
57+
expect(received[1].data).toEqual({ value: 2 });
58+
expect(received[2].data).toEqual({ value: 3 });
59+
60+
// Buffer should be empty after replay
61+
expect(buffer.getBufferedCount('test')).toBe(0);
62+
63+
buffer.close();
64+
});
65+
66+
it('should not replay to subsequent handlers for the same type', async () => {
67+
const buffer = createTypedMessageBuffer();
68+
const handler1Received: UserMessage[] = [];
69+
const handler2Received: UserMessage[] = [];
70+
71+
// Buffer message
72+
buffer.handleMessage(createMessage('test', { value: 'buffered' }));
73+
74+
// First handler gets replay
75+
buffer.onMessage('test', (msg) => handler1Received.push(msg));
76+
await new Promise((resolve) => setTimeout(resolve, 10));
77+
78+
// Second handler doesn't get replay
79+
buffer.onMessage('test', (msg) => handler2Received.push(msg));
80+
await new Promise((resolve) => setTimeout(resolve, 10));
81+
82+
expect(handler1Received).toHaveLength(1);
83+
expect(handler2Received).toHaveLength(0);
84+
85+
// New message goes to both
86+
buffer.handleMessage(createMessage('test', { value: 'new' }));
87+
88+
expect(handler1Received).toHaveLength(2);
89+
expect(handler2Received).toHaveLength(1);
90+
91+
buffer.close();
92+
});
93+
94+
it('should buffer different types independently', async () => {
95+
const buffer = createTypedMessageBuffer();
96+
const typeAReceived: UserMessage[] = [];
97+
const typeBReceived: UserMessage[] = [];
98+
99+
// Buffer messages of different types
100+
buffer.handleMessage(createMessage('type-a', { a: 1 }));
101+
buffer.handleMessage(createMessage('type-b', { b: 1 }));
102+
buffer.handleMessage(createMessage('type-a', { a: 2 }));
103+
104+
expect(buffer.getBufferedCount('type-a')).toBe(2);
105+
expect(buffer.getBufferedCount('type-b')).toBe(1);
106+
107+
// Register handler for type-a only
108+
buffer.onMessage('type-a', (msg) => typeAReceived.push(msg));
109+
await new Promise((resolve) => setTimeout(resolve, 10));
110+
111+
// type-a replayed, type-b still buffered
112+
expect(typeAReceived).toHaveLength(2);
113+
expect(buffer.getBufferedCount('type-a')).toBe(0);
114+
expect(buffer.getBufferedCount('type-b')).toBe(1);
115+
116+
// Now register handler for type-b
117+
buffer.onMessage('type-b', (msg) => typeBReceived.push(msg));
118+
await new Promise((resolve) => setTimeout(resolve, 10));
119+
120+
expect(typeBReceived).toHaveLength(1);
121+
expect(buffer.getBufferedCount('type-b')).toBe(0);
122+
123+
buffer.close();
124+
});
125+
});
126+
127+
describe('Buffer limits', () => {
128+
it('should respect maxPerType limit', () => {
129+
const buffer = createTypedMessageBuffer({ maxPerType: 3 });
130+
131+
for (let i = 0; i < 5; i++) {
132+
buffer.handleMessage(createMessage('test', { index: i }));
133+
}
134+
135+
expect(buffer.getBufferedCount('test')).toBe(3);
136+
137+
buffer.close();
138+
});
139+
140+
it('should respect maxTotal limit', () => {
141+
const buffer = createTypedMessageBuffer({ maxTotal: 5 });
142+
143+
for (let i = 0; i < 3; i++) {
144+
buffer.handleMessage(createMessage('type-a', { index: i }));
145+
}
146+
for (let i = 0; i < 5; i++) {
147+
buffer.handleMessage(createMessage('type-b', { index: i }));
148+
}
149+
150+
expect(buffer.getTotalBufferedCount()).toBe(5);
151+
152+
buffer.close();
153+
});
154+
155+
it('should drop old messages when buffer overflows', async () => {
156+
const buffer = createTypedMessageBuffer({ maxPerType: 3 });
157+
const received: UserMessage[] = [];
158+
159+
// Add 5 messages, only last 3 should be kept
160+
for (let i = 0; i < 5; i++) {
161+
buffer.handleMessage(createMessage('test', { index: i }));
162+
}
163+
164+
buffer.onMessage('test', (msg) => received.push(msg));
165+
await new Promise((resolve) => setTimeout(resolve, 10));
166+
167+
expect(received).toHaveLength(3);
168+
expect((received[0].data as any).index).toBe(2);
169+
expect((received[1].data as any).index).toBe(3);
170+
expect((received[2].data as any).index).toBe(4);
171+
172+
buffer.close();
173+
});
174+
175+
it('should drop messages older than maxAgeMs', async () => {
176+
const buffer = createTypedMessageBuffer({ maxAgeMs: 50 });
177+
178+
buffer.handleMessage(createMessage('test', { value: 'old' }));
179+
180+
// Wait for message to become stale
181+
await new Promise((resolve) => setTimeout(resolve, 100));
182+
183+
// Stale message should be cleaned up when we check
184+
expect(buffer.getBufferedCount('test')).toBe(0);
185+
186+
buffer.close();
187+
});
188+
});
189+
190+
describe('Handler removal', () => {
191+
it('should stop delivering to removed handlers', () => {
192+
const buffer = createTypedMessageBuffer();
193+
const received: UserMessage[] = [];
194+
195+
const sub = buffer.onMessage('test', (msg) => received.push(msg));
196+
buffer.handleMessage(createMessage('test', { value: 1 }));
197+
198+
expect(received).toHaveLength(1);
199+
200+
sub.remove();
201+
202+
buffer.handleMessage(createMessage('test', { value: 2 }));
203+
204+
expect(received).toHaveLength(1);
205+
206+
buffer.close();
207+
});
208+
209+
it('should buffer again if all handlers are removed', () => {
210+
const buffer = createTypedMessageBuffer();
211+
const received: UserMessage[] = [];
212+
213+
const sub = buffer.onMessage('test', (msg) => received.push(msg));
214+
buffer.handleMessage(createMessage('test', { value: 1 }));
215+
216+
sub.remove();
217+
218+
// New message should be buffered (no handlers)
219+
buffer.handleMessage(createMessage('test', { value: 2 }));
220+
221+
// Note: messages are only buffered until first handler is registered
222+
// After that, they go to handlers or are dropped if no handlers
223+
// This test verifies they're not delivered to removed handlers
224+
expect(received).toHaveLength(1);
225+
226+
buffer.close();
227+
});
228+
});
229+
230+
describe('Error handling', () => {
231+
it('should catch handler errors and continue to other handlers', () => {
232+
const buffer = createTypedMessageBuffer();
233+
const received: UserMessage[] = [];
234+
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
235+
236+
buffer.onMessage('test', () => {
237+
throw new Error('Handler error');
238+
});
239+
buffer.onMessage('test', (msg) => received.push(msg));
240+
241+
buffer.handleMessage(createMessage('test', { value: 1 }));
242+
243+
expect(received).toHaveLength(1);
244+
expect(consoleError).toHaveBeenCalled();
245+
246+
consoleError.mockRestore();
247+
buffer.close();
248+
});
249+
});
250+
251+
describe('Close', () => {
252+
it('should clear all state on close', () => {
253+
const buffer = createTypedMessageBuffer();
254+
255+
buffer.handleMessage(createMessage('test', { value: 1 }));
256+
buffer.onMessage('test', () => {});
257+
258+
buffer.close();
259+
260+
expect(buffer.getTotalBufferedCount()).toBe(0);
261+
262+
buffer.close();
263+
});
264+
265+
it('should not process messages after close', () => {
266+
const buffer = createTypedMessageBuffer();
267+
const received: UserMessage[] = [];
268+
269+
buffer.onMessage('test', (msg) => received.push(msg));
270+
buffer.close();
271+
272+
buffer.handleMessage(createMessage('test', { value: 1 }));
273+
274+
expect(received).toHaveLength(0);
275+
});
276+
});
277+
});
278+

0 commit comments

Comments
 (0)