Files
discord-rpg-concept/CLAUDE.md
2026-02-19 14:40:22 +01:00

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 bigint mode 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 in shared/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