6.9 KiB
6.9 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
# Development
bun --watch bot/index.ts # Run bot + API with hot reload
docker compose up # Start all services (bot, API, database)
docker compose up app # Start just the app (bot + API)
docker compose up db # Start just the database
# Testing
bun test # Run all tests
bun test path/to/file.test.ts # Run a single test file
bun test shared/modules/economy # Run tests in a directory
bun test --watch # Watch mode
# Database
bun run db:push:local # Push schema changes (local)
bun run db:studio # Open Drizzle Studio (localhost:4983)
bun run generate # Generate Drizzle migrations (Docker)
bun run migrate # Apply migrations (Docker)
# Admin Panel
bun run panel:dev # Start Vite dev server for dashboard
bun run panel:build # Build React dashboard for production
Architecture
Aurora is a Discord RPG bot + REST API running as a single Bun process. The bot and API share the same database client and services.
bot/ # Discord bot
├── commands/ # Slash commands by category (admin, economy, inventory, etc.)
├── events/ # Discord event handlers
├── lib/ # BotClient, handlers, loaders, embed helpers, commandUtils
├── modules/ # Feature modules (views, interactions per domain)
└── graphics/ # Canvas-based image generation (@napi-rs/canvas)
shared/ # Shared between bot and API
├── db/ # Drizzle ORM client + schema (users, economy, inventory, quests, etc.)
├── lib/ # env, config, errors, logger, types, utils
└── modules/ # Domain services (economy, user, inventory, quest, moderation, etc.)
api/ # REST API (Bun HTTP server)
└── src/routes/ # Route handlers for each domain
panel/ # React admin dashboard (Vite + Tailwind + Radix UI)
Key architectural details:
- Bot and API both import from
shared/— do not duplicate logic. - Services in
shared/modules/are singleton objects, not classes. - The database uses PostgreSQL 16+ via Drizzle ORM with
bigintmode for Discord IDs and currency. - Feature modules follow a strict file suffix convention (see below).
Import Conventions
Use path aliases (defined in tsconfig.json). Order: external packages → aliases → relative.
import { SlashCommandBuilder } from "discord.js"; // external
import { economyService } from "@shared/modules/economy/economy.service"; // alias
import { users } from "@db/schema"; // alias
import { createErrorEmbed } from "@lib/embeds"; // alias
import { localHelper } from "./helper"; // relative
Aliases:
@/*→bot/@shared/*→shared/@db/*→shared/db/@lib/*→bot/lib/@modules/*→bot/modules/@commands/*→bot/commands/
Code Patterns
Module File Suffixes
*.view.ts— Creates Discord embeds/components*.interaction.ts— Handles button/select/modal interactions*.service.ts— Business logic (lives inshared/modules/)*.types.ts— Module-specific TypeScript types*.test.ts— Tests (co-located with source)
Command Definition
export const commandName = createCommand({
data: new SlashCommandBuilder().setName("name").setDescription("desc"),
execute: async (interaction) => {
await withCommandErrorHandling(interaction, async () => {
const result = await service.method();
await interaction.editReply({ embeds: [createSuccessEmbed(result)] });
}, { ephemeral: true });
},
});
withCommandErrorHandling (from @lib/commandUtils) handles deferReply, UserError display, and unexpected error logging automatically.
Service Pattern
export const serviceName = {
methodName: async (params: ParamType): Promise<ReturnType> => {
return await withTransaction(async (tx) => {
// database operations
});
},
};
Error Handling
import { UserError, SystemError } from "@shared/lib/errors";
throw new UserError("You don't have enough coins!"); // shown to user
throw new SystemError("DB connection failed"); // logged, generic message shown
Database Transactions
import { withTransaction } from "@/lib/db";
return await withTransaction(async (tx) => {
const user = await tx.query.users.findFirst({ where: eq(users.id, id) });
await tx.update(users).set({ coins: newBalance }).where(eq(users.id, id));
return user;
}, existingTx); // pass existing tx for nested transactions
Testing
Mock modules before imports. Use bun:test.
import { describe, it, expect, mock, beforeEach } from "bun:test";
mock.module("@shared/db/DrizzleClient", () => ({
DrizzleClient: { query: mockQuery },
}));
describe("serviceName", () => {
beforeEach(() => mockFn.mockClear());
it("should handle expected case", async () => {
mockFn.mockResolvedValue(testData);
const result = await service.method(input);
expect(result).toEqual(expected);
});
});
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Files | camelCase or kebab-case | BotClient.ts, economy.service.ts |
| Classes | PascalCase | CommandHandler, UserError |
| Functions | camelCase | createCommand, handleShopInteraction |
| Constants | UPPER_SNAKE_CASE | EVENTS, BRANDING |
| Enums | PascalCase | TimerType, TransactionType |
| Services | camelCase singleton | economyService, userService |
| Types/Interfaces | PascalCase | Command, Event, GameConfigType |
| DB tables | snake_case | users, moderation_cases |
| Custom IDs | snake_case with prefix | shop_buy_, trade_accept_ |
| API routes | kebab-case | /api/guild-settings |
Key Files
| Purpose | File |
|---|---|
| Bot entry point | bot/index.ts |
| Discord client | bot/lib/BotClient.ts |
| DB schema index | shared/db/schema.ts |
| Error classes | shared/lib/errors.ts |
| Environment vars | shared/lib/env.ts |
| Config loader | shared/lib/config.ts |
| Embed helpers | bot/lib/embeds.ts |
| Command utils | bot/lib/commandUtils.ts |
| API server | api/src/server.ts |