feat: implement real-time event handling and SSE support; add event publishing and subscription mechanisms

This commit is contained in:
2026-01-17 01:39:06 +00:00
parent f3c0f492be
commit 56b1c40642
13 changed files with 473 additions and 13 deletions
+57
View File
@@ -0,0 +1,57 @@
import { describe, expect, it } from 'vitest';
import { buildGameEventChannel, type RealtimeEvent } from '@sammo-ts/common';
import { parseRealtimeEvent } from '../src/realtime/eventHub.js';
import { formatSseFrame } from '../src/realtime/sse.js';
describe('formatSseFrame', () => {
it('renders basic SSE payloads', () => {
const output = formatSseFrame({
event: 'ping',
id: '1',
retry: 1500,
data: 'ok',
});
expect(output).toBe(['event: ping', 'id: 1', 'retry: 1500', 'data: ok', ''].join('\n'));
});
it('splits multiline data', () => {
const output = formatSseFrame({
event: 'notice',
data: 'first\nsecond',
});
expect(output).toBe(['event: notice', 'data: first', 'data: second', ''].join('\n'));
});
});
describe('parseRealtimeEvent', () => {
it('accepts valid realtime events', () => {
const payload: RealtimeEvent = {
type: 'turnCompleted',
at: '2026-01-01T00:00:00.000Z',
result: {
lastTurnTime: '2026-01-01T00:00:00.000Z',
processedGenerals: 2,
processedTurns: 1,
durationMs: 1200,
partial: false,
},
};
const parsed = parseRealtimeEvent(JSON.stringify(payload));
expect(parsed?.type).toBe('turnCompleted');
});
it('rejects invalid payloads', () => {
expect(parseRealtimeEvent('not-json')).toBeNull();
expect(parseRealtimeEvent(JSON.stringify({}))).toBeNull();
});
});
describe('buildGameEventChannel', () => {
it('namespaces channels by profile', () => {
expect(buildGameEventChannel('che:default')).toBe('sammo:che:default:realtime:events');
});
});