Files
aurorabot/shared/lib/events.ts
syntaxbullet 0142508eb5 fix: add type safety and error handling to event bus
- Add DomainEventPayloads interface to events.ts for typed event payloads
- Wrap dashboard listeners with fireAndForget() to prevent unhandled promise rejections
- Type all listener parameters explicitly using DomainEventPayloads
- Add idempotency guard to registerDomainEventListeners to prevent double registration on hot-reload

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 13:04:32 +01:00

62 lines
2.4 KiB
TypeScript

import { EventEmitter } from "node:events";
import type { Transaction } from "@shared/lib/types";
/**
* Global system event bus for cross-module communication.
* Used for real-time dashboard updates and domain event decoupling.
*/
class SystemEventEmitter extends EventEmitter {
/**
* Emit an event and await all listeners sequentially.
* Used for domain events that must preserve transaction atomicity
* (e.g., quest progress tracking within the caller's DB transaction).
*/
async emitAsync(event: string, ...args: any[]): Promise<boolean> {
const listeners = this.listeners(event);
for (const listener of listeners) {
await (listener as Function)(...args);
}
return listeners.length > 0;
}
}
export const systemEvents = new SystemEventEmitter();
export const EVENTS = {
DASHBOARD: {
STATS_UPDATE: "dashboard:stats_update",
NEW_EVENT: "dashboard:new_event",
},
ACTIONS: {
RELOAD_COMMANDS: "actions:reload_commands",
CLEAR_CACHE: "actions:clear_cache",
MAINTENANCE_MODE: "actions:maintenance_mode",
},
QUEST: {
COMPLETED: "quest:completed",
},
DOMAIN: {
BALANCE_CHANGED: "domain:balance_changed",
XP_GAINED: "domain:xp_gained",
ITEM_COLLECTED: "domain:item_collected",
ITEM_USED: "domain:item_used",
TRANSFER_COMPLETED: "domain:transfer_completed",
DAILY_CLAIMED: "domain:daily_claimed",
TRIVIA_STARTED: "domain:trivia_started",
TRIVIA_WON: "domain:trivia_won",
EXAM_PASSED: "domain:exam_passed",
},
} as const;
export interface DomainEventPayloads {
[EVENTS.DOMAIN.BALANCE_CHANGED]: { userId: string; type: string; tx: Transaction };
[EVENTS.DOMAIN.XP_GAINED]: { userId: string; amount: number; tx: Transaction };
[EVENTS.DOMAIN.ITEM_COLLECTED]: { userId: string; itemId: number; quantity: number; tx: Transaction };
[EVENTS.DOMAIN.ITEM_USED]: { userId: string; itemId: number; tx: Transaction };
[EVENTS.DOMAIN.TRANSFER_COMPLETED]: { username: string; amount: bigint; toUserId: string };
[EVENTS.DOMAIN.DAILY_CLAIMED]: { username: string; amount: bigint };
[EVENTS.DOMAIN.TRIVIA_STARTED]: { username: string; difficulty: string };
[EVENTS.DOMAIN.TRIVIA_WON]: { username: string | undefined; reward: bigint };
[EVENTS.DOMAIN.EXAM_PASSED]: { username: string; reward: bigint };
}