import { createRouteBody } from '../products/serving/routes.js'; import { streamSSE } from 'hono/streaming'; import { z } from '@hono/zod-openapi'; import { appEvents } from '../events.js'; import { logger } from '../commons/logger.js'; export const getStreamRoute = createRouteBody('get', '/api/stream', ['System'], 'Stream System Events', 'Subscribe to real-time updates for categories, posts, and pages.', undefined, { 200: { description: 'Event Stream', content: { 'text/event-stream': { schema: z.string() } } } }, true // public ); // Track active connections const connectedClients = new Set(); // Single listener for the entire application const broadcastAppUpdate = async (event) => { const payload = JSON.stringify(event); for (const client of connectedClients) { try { await client.stream.writeSSE({ event: event.kind, data: payload }); } catch (err) { logger.error({ err, clientId: client.id }, 'Error broadcasting to stream'); // Client will be removed by the onAbort handler in the stream handler } } }; // Subscribe once appEvents.on('app-update', broadcastAppUpdate); export const streamHandler = async (c) => { return streamSSE(c, async (stream) => { const id = crypto.randomUUID(); const client = { id, stream }; connectedClients.add(client); // Send initial connection message await stream.writeSSE({ event: 'connected', data: JSON.stringify({ message: 'Connected to event stream', clientId: id }) }); // Keep connection alive & handle cleanup let interval; const heartbeatInterval = parseInt(process.env.STREAM_HEARTBEAT_INTERVAL_MS || '30000', 10); // Send heartbeat to prevent timeouts interval = setInterval(async () => { try { await stream.writeSSE({ event: 'ping', data: '' }); } catch (e) { // connection likely closed } }, heartbeatInterval); // Wait until the stream is aborted await new Promise((resolve) => { stream.onAbort(() => { connectedClients.delete(client); clearInterval(interval); resolve(); }); }); }); }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RyZWFtLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2VuZHBvaW50cy9zdHJlYW0udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBRWhFLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUMzQyxPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDdEMsT0FBTyxFQUFFLFNBQVMsRUFBWSxNQUFNLGNBQWMsQ0FBQztBQUNuRCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFFOUMsTUFBTSxDQUFDLE1BQU0sY0FBYyxHQUFHLGVBQWUsQ0FDekMsS0FBSyxFQUNMLGFBQWEsRUFDYixDQUFDLFFBQVEsQ0FBQyxFQUNWLHNCQUFzQixFQUN0QixrRUFBa0UsRUFDbEUsU0FBUyxFQUNUO0lBQ0ksR0FBRyxFQUFFO1FBQ0QsV0FBVyxFQUFFLGNBQWM7UUFDM0IsT0FBTyxFQUFFO1lBQ0wsbUJBQW1CLEVBQUU7Z0JBQ2pCLE1BQU0sRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFO2FBQ3JCO1NBQ0o7S0FDSjtDQUNKLEVBQ0QsSUFBSSxDQUFDLFNBQVM7Q0FDakIsQ0FBQztBQUVGLDJCQUEyQjtBQUMzQixNQUFNLGdCQUFnQixHQUFHLElBQUksR0FBRyxFQUc1QixDQUFDO0FBRUwsNkNBQTZDO0FBQzdDLE1BQU0sa0JBQWtCLEdBQUcsS0FBSyxFQUFFLEtBQWUsRUFBRSxFQUFFO0lBQ2pELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDdEMsS0FBSyxNQUFNLE1BQU0sSUFBSSxnQkFBZ0IsRUFBRSxDQUFDO1FBQ3BDLElBQUksQ0FBQztZQUNELE1BQU0sTUFBTSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUM7Z0JBQ3pCLEtBQUssRUFBRSxLQUFLLENBQUMsSUFBSTtnQkFDakIsSUFBSSxFQUFFLE9BQU87YUFDaEIsQ0FBQyxDQUFDO1FBQ1AsQ0FBQztRQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7WUFDWCxNQUFNLENBQUMsS0FBSyxDQUFDLEVBQUUsR0FBRyxFQUFFLFFBQVEsRUFBRSxNQUFNLENBQUMsRUFBRSxFQUFFLEVBQUUsOEJBQThCLENBQUMsQ0FBQztZQUMzRSxzRUFBc0U7UUFDMUUsQ0FBQztJQUNMLENBQUM7QUFDTCxDQUFDLENBQUM7QUFFRixpQkFBaUI7QUFDakIsU0FBUyxDQUFDLEVBQUUsQ0FBQyxZQUFZLEVBQUUsa0JBQWtCLENBQUMsQ0FBQztBQUUvQyxNQUFNLENBQUMsTUFBTSxhQUFhLEdBQUcsS0FBSyxFQUFFLENBQVUsRUFBRSxFQUFFO0lBQzlDLE9BQU8sU0FBUyxDQUFDLENBQUMsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDakMsTUFBTSxFQUFFLEdBQUcsTUFBTSxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBQy9CLE1BQU0sTUFBTSxHQUFHLEVBQUUsRUFBRSxFQUFFLE1BQU0sRUFBRSxDQUFDO1FBRTlCLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUM3QixrQ0FBa0M7UUFDbEMsTUFBTSxNQUFNLENBQUMsUUFBUSxDQUFDO1lBQ2xCLEtBQUssRUFBRSxXQUFXO1lBQ2xCLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsT0FBTyxFQUFFLDJCQUEyQixFQUFFLFFBQVEsRUFBRSxFQUFFLEVBQUUsQ0FBQztTQUMvRSxDQUFDLENBQUM7UUFFSCx5Q0FBeUM7UUFDekMsSUFBSSxRQUF3QixDQUFDO1FBQzdCLE1BQU0saUJBQWlCLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsNEJBQTRCLElBQUksT0FBTyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBRTVGLHFDQUFxQztRQUNyQyxRQUFRLEdBQUcsV0FBVyxDQUFDLEtBQUssSUFBSSxFQUFFO1lBQzlCLElBQUksQ0FBQztnQkFDRCxNQUFNLE1BQU0sQ0FBQyxRQUFRLENBQUMsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxFQUFFLEVBQUUsQ0FBQyxDQUFDO1lBQ3ZELENBQUM7WUFBQyxPQUFPLENBQUMsRUFBRSxDQUFDO2dCQUNULDJCQUEyQjtZQUMvQixDQUFDO1FBQ0wsQ0FBQyxFQUFFLGlCQUFpQixDQUFDLENBQUM7UUFFdEIsbUNBQW1DO1FBQ25DLE1BQU0sSUFBSSxPQUFPLENBQU8sQ0FBQyxPQUFPLEVBQUUsRUFBRTtZQUNoQyxNQUFNLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRTtnQkFDaEIsZ0JBQWdCLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDO2dCQUNoQyxhQUFhLENBQUMsUUFBUSxDQUFDLENBQUM7Z0JBQ3hCLE9BQU8sRUFBRSxDQUFDO1lBQ2QsQ0FBQyxDQUFDLENBQUM7UUFDUCxDQUFDLENBQUMsQ0FBQztJQUNQLENBQUMsQ0FBQyxDQUFDO0FBQ1AsQ0FBQyxDQUFDIn0=