Unified real-time sync — the single source of truth
Service emits a domain event → SyncListener handles fan-out to WebSocket rooms. The same map drives every cache invalidation in the app. 41 @OnEvent handlers, all try/catch, multi-room broadcast.
- Service never calls socket.to().emit() directly — emits a domain event instead
- Each handler wraps in try/catch so sync failure never compromises a DB transaction
- Same handler routes to multiple rooms (project room + client room + admin room)
@OnEvent('project.updated')
handleProjectUpdated(payload: {
projectId: number;
clientId: number;
title: string;
status: string;
}) {
try {
this.logger.log(`🔄 Syncing project ${payload.projectId}: ${payload.status}`);
// Notify the project room (admin / uploader in detail view)
this.assetsGateway.server
.to(`project_${payload.projectId}`)
.emit('dashboard_sync', {
type: 'project_updated',
projectId: payload.projectId,
status: payload.status,
projectTitle: payload.title,
});
// Notify the client (portal dashboard)
this.assetsGateway.emitToClient(
payload.clientId,
'dashboard_sync',
{
type: 'project_updated',
projectId: payload.projectId,
projectTitle: payload.title,
status: payload.status,
}
);
} catch (error) {
const err = error as Error;
this.logger.error(
`Error in handleProjectUpdated: ${err.message}`,
err.stack
);
}
}