Files
tiennm99 e2e3112eb5 feat(db): finalize MongoDB Atlas migration documentation
Phase 08: Complete documentation pass for MongoDB Atlas migration.

- Create docs/cost-tracking.md: Cost monitoring, upgrade triggers, monthly checklist
- Create docs/project-changelog.md: Full migration summary with phase breakdown
- Update docs/architecture.md section 8: Describe dual-write era, MongoDB store layers
- Update docs/code-standards.md: Add Persistence section for storage factory patterns
- Update docs/codebase-summary.md: Reflect MongoDB as primary, update test count (733)
- Update README.md: Storage section now describes MongoDB + dual-write during migration
- Update CLAUDE.md: Architecture section references MongoDB instead of KV/D1
- Update tests/fakes/fake-mongo.js: Document frozen surface (Phase 02-08 API)

Verified:
- All 733 tests passing
- Lint + secret-leak check pass
- npm run register:dry succeeds
- Auto-pause concern satisfied: trading (17:00), lolschedule (01:00), drift-verifier (hourly) all write to Mongo
- Roadmap verified migration NOT listed (future-only per user feedback)

Post-Phase-07 cutover: dual-write collapses, KV/D1 deleted, MongoDB becomes sole backend.
2026-04-26 09:34:25 +07:00

4.6 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

npm run dev          # local dev server (wrangler dev) at http://localhost:8787
npm run lint         # biome check src tests scripts + eslint src
npm run format       # biome format --write
npm test             # vitest run (all tests)
npx vitest run tests/modules/trading/format.test.js   # single test file
npx vitest run -t "formats with dot"                  # single test by name
npm run db:migrate   # apply migrations to D1 (prod)
npm run db:migrate -- --local                         # apply to local dev D1
npm run db:migrate -- --dry-run                       # preview without applying
npm run deploy       # wrangler deploy + db:migrate + register webhook/commands
npm run register:dry # preview setWebhook + setMyCommands payloads without calling Telegram

Architecture

grammY Telegram bot on Cloudflare Workers. Modules are plug-n-play: each module is a folder under src/modules/ that exports { name, commands[], init? }. A single MODULES env var controls which modules are loaded.

Request flow: POST /webhook → grammY validates secret header → getBot(env) (memoized per warm instance) → installDispatcher builds registry on first call → bot.command(name, handler) for every command → handler runs.

Key abstractions:

  • src/modules/registry.js — loads modules from static import map (src/modules/index.js), validates commands, detects name conflicts across all visibility levels, builds four maps (public/protected/private/all). Memoized via getCurrentRegistry().
  • src/db/create-store.js — returns a storage interface (either MongoKVStore via dual-write wrapper, or direct MongoKVStore depending on STORAGE_PRIMARY flag) with auto-prefixed keys per module (moduleName:key). Modules never touch env.KV, env.DB, or env.MONGODB_URI directly.
  • scripts/register.js — post-deploy script that imports the same registry to derive public commands, then calls Telegram setWebhook + setMyCommands. Uses stub-kv.js to satisfy KV binding without real IO.

Three command visibilities: public (in Telegram / menu + /help), protected (in /help only), private (hidden easter eggs). All three are registered via bot.command() — visibility controls discoverability, not access.

Adding a Module

  1. Create src/modules/<name>/index.js with default export { name, commands, init? }
  2. Add one line to src/modules/index.js static import map
  3. Add <name> to MODULES in wrangler.toml [vars]
  4. Full guide: docs/adding-a-module.md

Module Contract

{
  name: "mymod",                              // must match folder + import map key
  init: async ({ db, sql, env }) => { ... },  // optional — db: KVStore, sql: SqlStore|null
  commands: [{
    name: "mycmd",                            // ^[a-z0-9_]{1,32}$, no leading slash
    visibility: "public",                     // "public" | "protected" | "private"
    description: "Does a thing",              // required for all visibilities
    handler: async (ctx) => { ... },          // grammY context
  }],
  crons: [{                                   // optional scheduled jobs
    schedule: "0 17 * * *",                   // cron expression
    name: "daily-cleanup",                    // unique within module
    handler: async (event, ctx) => { ... },   // receives { db, sql, env }
  }],
}
  • Command names must be globally unique across ALL modules and visibilities. Conflicts throw at load time.
  • Cron schedules declared here MUST also be registered in wrangler.toml [triggers] crons.
  • For D1 setup (migrations, table naming), see docs/using-d1.md.
  • For cron syntax and testing, see docs/using-cron.md.

Testing

Pure-logic unit tests only — no workerd, no Telegram fixtures. Tests use fakes from tests/fakes/ (fake-kv-namespace, fake-bot, fake-modules) injected via parameters, not vi.mock.

For modules that call fetch (like trading/prices), stub global.fetch with vi.fn() in tests.

Code Style

Biome enforces: 2-space indent, double quotes, semicolons, trailing commas, 100-char line width, sorted imports. Run npm run format before committing. Keep files under 200 lines — split into focused submodules when approaching the limit.

Environment

  • Secrets (TELEGRAM_BOT_TOKEN, TELEGRAM_WEBHOOK_SECRET): set via wrangler secret put, mirrored in .env.deploy (gitignored) for register script
  • .dev.vars: local dev secrets (gitignored), copy from .dev.vars.example
  • Node >=20.6 required (for --env-file flag)