refactor: replace dynamic imports with event bus pattern

Replace 12 dynamic `await import()` calls with domain events emitted
through the existing systemEvents bus, breaking circular dependencies
between services (economy/inventory/leveling -> quest, * -> dashboard).

- Add `emitAsync` to SystemEventEmitter for sequential listener awaiting,
  preserving DB transaction atomicity for quest progress tracking
- Add DOMAIN event constants (BALANCE_CHANGED, XP_GAINED, ITEM_COLLECTED,
  ITEM_USED, TRANSFER_COMPLETED, DAILY_CLAIMED, TRIVIA_*, EXAM_PASSED)
- Create shared/lib/eventWiring.ts to register all domain event listeners
- Convert quest event calls to `await systemEvents.emitAsync()` (5 calls)
- Convert dashboard event calls to `systemEvents.emit()` fire-and-forget (5 calls)
- Convert exam.service.ts userService import to static import (1 call)
- Convert dashboard.service.ts events import to static import (1 call)
- Leave inventory.service.ts validateAndExecuteEffect import unchanged (Task 3)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
syntaxbullet
2026-03-18 12:59:15 +01:00
parent a96c6caa49
commit 5863418ae9
9 changed files with 120 additions and 45 deletions

View File

@@ -1,6 +1,7 @@
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { users, transactions, moderationCases, inventory, lootdrops, items, type User } from "@db/schema";
import { desc, sql, gte, eq } from "drizzle-orm";
import { systemEvents, EVENTS } from "@shared/lib/events";
import type { RecentEvent, ActivityData } from "./dashboard.types";
import { TransactionType } from "@shared/lib/constants";
@@ -139,7 +140,6 @@ export const dashboardService = {
// Broadcast to WebSocket clients
try {
const { systemEvents, EVENTS } = await import("@shared/lib/events");
systemEvents.emit(EVENTS.DASHBOARD.NEW_EVENT, {
...fullEvent,
timestamp: (fullEvent.timestamp instanceof Date)

View File

@@ -1,6 +1,7 @@
import { users, transactions, userTimers } from "@db/schema";
import { eq, sql, and } from "drizzle-orm";
import { config } from "@shared/lib/config";
import { systemEvents, EVENTS } from "@shared/lib/events";
import { withTransaction } from "@/lib/db";
import type { Transaction } from "@shared/lib/types";
import { UserError } from "@shared/lib/errors";
@@ -62,12 +63,7 @@ export const economyService = {
});
// Record dashboard event
const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service");
await dashboardService.recordEvent({
type: 'info',
message: `${sender.username} transferred ${amount.toLocaleString()} AU to User ID ${toUserId}`,
icon: '💸'
});
systemEvents.emit(EVENTS.DOMAIN.TRANSFER_COMPLETED, { username: sender.username, amount, toUserId });
return { success: true, amount };
}, tx);
@@ -158,12 +154,7 @@ export const economyService = {
});
// Record dashboard event
const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service");
await dashboardService.recordEvent({
type: 'success',
message: `${user.username} claimed daily reward: ${totalReward.toLocaleString()} AU`,
icon: '☀️'
});
systemEvents.emit(EVENTS.DOMAIN.DAILY_CLAIMED, { username: user.username, amount: totalReward });
return { claimed: true, amount: totalReward, streak, nextReadyAt, isWeekly: isWeeklyCurrent, weeklyBonus: weeklyBonusAmount };
}, tx);
@@ -197,8 +188,7 @@ export const economyService = {
});
// Trigger Quest Event
const { questService } = await import("@shared/modules/quest/quest.service");
await questService.handleEvent(id, type, 1, txFn);
await systemEvents.emitAsync(EVENTS.DOMAIN.BALANCE_CHANGED, { userId: id, type, tx: txFn });
return user;
}, tx);

View File

@@ -2,6 +2,8 @@ import { users, userTimers, transactions } from "@db/schema";
import { eq, and, sql } from "drizzle-orm";
import { TimerType, TransactionType } from "@shared/lib/constants";
import { config } from "@shared/lib/config";
import { systemEvents, EVENTS } from "@shared/lib/events";
import { userService } from "@shared/modules/user/user.service";
import { withTransaction } from "@/lib/db";
import type { Transaction } from "@shared/lib/types";
import { UserError, SystemError } from "@shared/lib/errors";
@@ -84,7 +86,6 @@ export const examService = {
async registerForExam(userId: string, username: string, tx?: Transaction): Promise<ExamActionResult> {
return await withTransaction(async (txFn) => {
// Ensure user exists
const { userService } = await import("@shared/modules/user/user.service");
const user = await userService.getOrCreateUser(userId, username, txFn);
if (!user) throw new SystemError("Failed to get or create user.");
@@ -242,12 +243,7 @@ export const examService = {
}
// Record dashboard event
const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service");
await dashboardService.recordEvent({
type: 'success',
message: `${user.username} passed their exam: ${reward.toLocaleString()} AU`,
icon: '🎓'
});
systemEvents.emit(EVENTS.DOMAIN.EXAM_PASSED, { username: user.username, reward });
return {
status: ExamStatus.AVAILABLE,

View File

@@ -4,6 +4,7 @@ import { DrizzleClient } from "@shared/db/DrizzleClient";
import { economyService } from "@shared/modules/economy/economy.service";
import { levelingService } from "@shared/modules/leveling/leveling.service";
import { config } from "@shared/lib/config";
import { systemEvents, EVENTS } from "@shared/lib/events";
import { UserError } from "@shared/lib/errors";
import { withTransaction } from "@/lib/db";
import type { Transaction, ItemUsageData } from "@shared/lib/types";
@@ -39,8 +40,7 @@ export const inventoryService = {
.returning();
// Trigger Quest Event
const { questService } = await import("@shared/modules/quest/quest.service");
await questService.handleEvent(userId, `ITEM_COLLECT:${itemId}`, Number(quantity), txFn);
await systemEvents.emitAsync(EVENTS.DOMAIN.ITEM_COLLECTED, { userId, itemId, quantity: Number(quantity), tx: txFn });
return entry;
} else {
@@ -67,8 +67,7 @@ export const inventoryService = {
.returning();
// Trigger Quest Event
const { questService } = await import("@shared/modules/quest/quest.service");
await questService.handleEvent(userId, `ITEM_COLLECT:${itemId}`, Number(quantity), txFn);
await systemEvents.emitAsync(EVENTS.DOMAIN.ITEM_COLLECTED, { userId, itemId, quantity: Number(quantity), tx: txFn });
return entry;
}
@@ -184,8 +183,7 @@ export const inventoryService = {
}
// Trigger Quest Event
const { questService } = await import("@shared/modules/quest/quest.service");
await questService.handleEvent(userId, `ITEM_USE:${itemId}`, 1, txFn);
await systemEvents.emitAsync(EVENTS.DOMAIN.ITEM_USED, { userId, itemId, tx: txFn });
return { success: true, results, usageData, item };
}, tx);

View File

@@ -2,6 +2,7 @@ import { users, userTimers } from "@db/schema";
import { eq, sql, and } from "drizzle-orm";
import { withTransaction } from "@/lib/db";
import { config } from "@shared/lib/config";
import { systemEvents, EVENTS } from "@shared/lib/events";
import type { Transaction } from "@shared/lib/types";
import { TimerKey, TimerType } from "@shared/lib/constants";
import { UserError } from "@shared/lib/errors";
@@ -70,8 +71,7 @@ export const levelingService = {
.returning();
// Trigger Quest Event
const { questService } = await import("@shared/modules/quest/quest.service");
await questService.handleEvent(id, 'XP_GAIN', Number(amount), txFn);
await systemEvents.emitAsync(EVENTS.DOMAIN.XP_GAINED, { userId: id, amount: Number(amount), tx: txFn });
return { user: updatedUser, levelUp, currentLevel: newLevel };
}, tx);

View File

@@ -1,6 +1,7 @@
import { users, userTimers, transactions } from "@db/schema";
import { eq, and, sql } from "drizzle-orm";
import { config } from "@shared/lib/config";
import { systemEvents, EVENTS } from "@shared/lib/events";
import { withTransaction } from "@/lib/db";
import type { Transaction } from "@shared/lib/types";
import { UserError, SystemError } from "@shared/lib/errors";
@@ -232,12 +233,7 @@ class TriviaService {
});
// Record dashboard event
const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service");
await dashboardService.recordEvent({
type: 'info',
message: `${username} started a trivia game (${question.difficulty})`,
icon: '🎯'
});
systemEvents.emit(EVENTS.DOMAIN.TRIVIA_STARTED, { username, difficulty: question.difficulty });
return session;
});
@@ -293,12 +289,7 @@ class TriviaService {
where: eq(users.id, BigInt(userId))
});
const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service");
await dashboardService.recordEvent({
type: 'success',
message: `${user?.username} won ${reward.toLocaleString()} AU from trivia!`,
icon: '🎉'
});
systemEvents.emit(EVENTS.DOMAIN.TRIVIA_WON, { username: user?.username, reward });
});
}