feat(web): bridge logs to typed event stream
This commit is contained in:
1 parent
d6d29badcd
commit
9919b12aab
3 files changed
+41
-1
No files matched your search
@@ -1,5 +1,7 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import type { Request, RequestHandler, Response } from 'express';
|
||||
import type { LogEntry } from './types.js';
|
||||
import { RealtimeEventHub } from './events/index.js';
|
||||
import { createExpressSseHandler } from './events/express-sse-adapter.js';
|
||||
|
||||
const MAX_LOGS = 500;
|
||||
const INITIAL_LOGS_ON_CONNECT = 50;
|
||||
@@ -11,6 +13,13 @@ const INITIAL_LOGS_ON_CONNECT = 50;
|
||||
export class LogBroadcaster {
|
||||
private entries: LogEntry[] = [];
|
||||
private clients = new Set<Response>();
|
||||
private readonly eventHub: RealtimeEventHub;
|
||||
readonly events: RequestHandler;
|
||||
|
||||
constructor(eventHub?: RealtimeEventHub) {
|
||||
this.eventHub = eventHub ?? new RealtimeEventHub({ capacity: MAX_LOGS });
|
||||
this.events = createExpressSseHandler({ hub: this.eventHub });
|
||||
}
|
||||
|
||||
push(level: LogEntry['level'], message: string, data?: unknown): void {
|
||||
const entry: LogEntry = { timestamp: Date.now(), level, message, data };
|
||||
@@ -19,6 +28,10 @@ export class LogBroadcaster {
|
||||
this.entries = this.entries.slice(-MAX_LOGS);
|
||||
}
|
||||
|
||||
if (!this.eventHub.closed) {
|
||||
this.eventHub.publish('log.created', { level, message, data });
|
||||
}
|
||||
|
||||
this.fanout(JSON.stringify({ type: 'log', ...entry }));
|
||||
}
|
||||
|
||||
@@ -76,5 +89,6 @@ export class LogBroadcaster {
|
||||
}
|
||||
}
|
||||
this.clients.clear();
|
||||
this.eventHub.close();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import type { WebContext } from '../context.js';
|
||||
export function createLogsRouter(ctx: WebContext): Router {
|
||||
const router = Router();
|
||||
router.get('/logs/stream', ctx.broadcaster.stream);
|
||||
router.get('/events/stream', ctx.broadcaster.events);
|
||||
router.get('/logs', ctx.broadcaster.recent);
|
||||
return router;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { RealtimeEventHub, type RealtimeEvent } from '../src/web/events/index.js';
|
||||
import { LogBroadcaster } from '../src/web/log-broadcaster.js';
|
||||
|
||||
describe('LogBroadcaster realtime bridge', () => {
|
||||
it('publishes each log through the typed event hub', () => {
|
||||
const hub = new RealtimeEventHub({ capacity: 10 });
|
||||
const received: RealtimeEvent[] = [];
|
||||
hub.subscribe((event) => received.push(event));
|
||||
const broadcaster = new LogBroadcaster(hub);
|
||||
|
||||
broadcaster.push('warn', 'Connection is stale', { attempts: 2 });
|
||||
|
||||
expect(received).toEqual([
|
||||
expect.objectContaining({
|
||||
type: 'log.created',
|
||||
payload: {
|
||||
level: 'warn',
|
||||
message: 'Connection is stale',
|
||||
data: { attempts: 2 },
|
||||
},
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user