Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/services/dashboard/dashboardEvents.server.ts
T

49 lines
2.0 KiB
TypeScript

// R-P11b — tenant-wide dashboard live pub/sub. Reuses the repo's existing
// in-process EventEmitter (`~/utils/stream/emitter.server`, the same one behind
// the workflow-execution and R-P8b pipeline-board SSE streams) so relevant
// mutations push a change signal to connected dashboard SSE clients, which then
// re-send a fresh permission-gated snapshot instead of staying frozen on the
// connect-time snapshot. No new infrastructure is introduced.
//
// Scope caveat (same as projectBoardEvents.server): the emitter is per web
// process. With a single web instance every dashboard client shares the
// process; horizontal scaling would need a cross-process transport (e.g. Redis
// pub/sub) behind the same publish/subscribe API — tracked follow-up, out of
// this ticket's scope.
import { emitter } from "~/utils/stream/emitter.server";
/** Sources that currently publish dashboard-relevant changes. */
export type DashboardChangeSource = "projects";
export interface DashboardChange {
readonly source: DashboardChangeSource;
readonly at: string;
}
/** In-process channel name for one tenant's dashboard change feed. */
export function dashboardChannel(tenantId: string): string {
return `dashboard:${tenantId}`;
}
let listenerCapRaised = false;
/** Subscribe an SSE stream to a tenant's dashboard changes. Returns an unsubscribe fn. */
export function subscribeToDashboardChanges(tenantId: string, onChange: (payload: string) => void): () => void {
if (!listenerCapRaised) {
emitter.setMaxListeners(0);
listenerCapRaised = true;
}
const channel = dashboardChannel(tenantId);
emitter.on(channel, onChange);
return () => {
emitter.off(channel, onChange);
};
}
/** Publish a dashboard-relevant change so connected clients receive a fresh snapshot. */
export function publishDashboardChange(input: { tenantId: string; source: DashboardChangeSource }): void {
const payload: DashboardChange = { source: input.source, at: new Date().toISOString() };
emitter.emit(dashboardChannel(input.tenantId), JSON.stringify(payload));
}