/** * HMR hub — tracks connected browser HMR sockets and broadcasts update events. * * Each open page holds one WebSocket to `/__wrnexus/hmr`. The in-process file * watcher (see index.ts) classifies a change and broadcasts a typed message: * * { type: "css" } -> the browser hot-swaps the stylesheet (no reload) * { type: "reload" } -> the browser asks for fresh HTML over the HMR socket * * Page/component/API/middleware/realtime changes invalidate their modules and * broadcast `reload` without closing the server or WebSocket. The browser asks * the same process for fresh HTML and performs a soft DOM morph. */ export type HmrMessage = { type: "css"; version: number } | { type: "reload"; version: number }; /** Minimal shape of a Bun ServerWebSocket we rely on. */ interface Socket { send(data: string): unknown; } export class HmrHub { private sockets = new Set(); private version = 0; add(ws: Socket): void { this.sockets.add(ws); } remove(ws: Socket): void { this.sockets.delete(ws); } broadcast(message: HmrMessage): void { const payload = JSON.stringify(message); for (const ws of this.sockets) { try { ws.send(payload); } catch { this.sockets.delete(ws); } } } get size(): number { return this.sockets.size; } css(): void { this.broadcast({ type: "css", version: ++this.version }); } reload(): void { this.broadcast({ type: "reload", version: ++this.version }); } }