Some checks failed
Deploy to Production / test (push) Failing after 31s
These domain events were only connected to dashboard recording but never called questService.handleEvent(), so quests with triggers TRANSFER_OUT, DAILY_REWARD, TRIVIA_WIN, and EXAM_REWARD never tracked progress. Added userId and tx to event payloads and switched from emit to emitAsync for transaction atomicity. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
62 lines
2.5 KiB
TypeScript
62 lines
2.5 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]: { userId: string; username: string; amount: bigint; toUserId: string; tx: Transaction };
|
|
[EVENTS.DOMAIN.DAILY_CLAIMED]: { userId: string; username: string; amount: bigint; tx: Transaction };
|
|
[EVENTS.DOMAIN.TRIVIA_STARTED]: { username: string; difficulty: string };
|
|
[EVENTS.DOMAIN.TRIVIA_WON]: { userId: string; username: string | undefined; reward: bigint; tx: Transaction };
|
|
[EVENTS.DOMAIN.EXAM_PASSED]: { userId: string; username: string; reward: bigint; tx: Transaction };
|
|
}
|