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

5.4 KiB

Code Standards

Language & Runtime

  • JavaScript (ES modules, "type": "module" in package.json)
  • No TypeScript — JSDoc typedefs for type contracts (see kv-store-interface.js, registry.js)
  • Cloudflare Workers runtime — Web APIs only, no Node.js built-ins, no nodejs_compat
  • grammY for Telegram bot framework

Formatting (Biome)

Enforced by npm run lint / npm run format:

  • 2-space indent
  • Double quotes
  • Semicolons: always
  • Trailing commas: all
  • Line width: 100 characters
  • Imports: auto-sorted by Biome

Run npm run format before committing.

JSDoc & Type Definitions

  • Central typedefs location: src/types.js — all module-level typedefs live here (Env, Module, Command, Cron, ModuleContext, SqlStore, KVStore, Trade, Portfolio, etc.).
  • When to add JSDoc: Required on exported functions, types, and public module interfaces. Optional on internal helpers (< 5 lines, obviously self-documenting).
  • Validation: ESLint (eslint src) enforces valid JSDoc syntax. Run npm run lint to check.
  • No TypeScript: JSDoc + .js files only. Full type info available to editor tooling without a build step.
  • Example:
    /**
     * Validate a trade before insertion.
     *
     * @param {Trade} trade
     * @returns {boolean}
     */
    function isValidTrade(trade) {
      return trade.qty > 0 && trade.priceVnd > 0;
    }
    

File Organization

  • Max 200 lines per code file. Split into focused submodules when approaching the limit.
  • Module code lives in src/modules/<name>/ — one folder per module.
  • Shared utilities in src/util/.
  • DB layer in src/db/.
  • Tests mirror source structure: tests/modules/<name>/, tests/db/, tests/util/.

Naming Conventions

  • Files: lowercase, hyphens for multi-word (stats-handler.js, fake-kv-namespace.js)
  • Directories: lowercase, single word preferred (trading/, util/)
  • Functions/variables: camelCase
  • Constants: UPPER_SNAKE_CASE for frozen config objects (e.g. CURRENCIES)
  • Command names: lowercase + digits + underscore, 1-32 chars, no leading slash

Module Conventions

Every module default export must have:

export default {
  name: "modname",     // === folder name === import map key
  commands: [...],     // validated at load time
  init: async ({ db, sql, env }) => { ... },  // optional
  crons: [...],        // optional scheduled jobs
};
  • Store module-level db and sql references in closure variables, set during init
  • Never access env.KV, env.DB, or env.MONGODB_URI directly — always use the prefixed db (KVStore) or sql (SqlStore) from init
  • sql is null when no relational store is bound — always guard with if (!sql) return
  • Command handlers receive grammY ctx — use ctx.match for command arguments, ctx.from.id for user identity
  • Reply with ctx.reply(text) — plain text or Telegram HTML
  • Cron handlers receive (event, { db, sql, env }) — same context as init

Persistence Layer

All data persistence flows through storage factories:

  • createStore(moduleName, env) — returns a KVStore interface for key-value data (simple state, settings, JSON blobs). Implementation: MongoKVStore (primary) with optional dual-write to Cloudflare KV during migration.
  • createSqlStore(moduleName, env) — returns a SqlStore interface for relational data (trading ledger, aggregates, scans). Implementation: MongoTradesStore (MongoDB native queries and inserts). D1 is read-only during migration.
  • Modules NEVER instantiate MongoClient directly. All MongoDB access goes through MongoKVStore or MongoTradesStore factories.

Post-migration (after Phase 07 cutover), the dual-write layer collapses and Cloudflare KV/D1 are deleted; createStore returns MongoKVStore directly.

Error Handling

  • Load-time failures (bad module, command conflicts, missing env): throw immediately — fail loud at deploy, not at runtime.
  • Handler-level errors (API failures, bad user input): catch and reply with user-friendly message. Never crash the handler — grammY logs unhandled rejections but the user sees nothing.
  • KV failures: best-effort writes (wrap in try/catch), guard reads with ?. and null coalescing.
  • getJSON swallows corrupt JSON and returns null — modules must handle null gracefully.

Testing

  • Framework: Vitest
  • Style: Pure-logic unit tests. No workerd, no Telegram integration, no network calls.
  • Fakes: tests/fakes/ provides fake-kv-namespace.js, fake-bot.js, fake-modules.js. Inject via parameters, not vi.mock.
  • External APIs: Stub global.fetch with vi.fn() returning canned responses.
  • Coverage: npx vitest run --coverage (v8 provider, text + HTML output).

Commit Messages

Conventional commits:

feat: add paper trading module
fix: handle null price in sell handler
docs: update architecture for trading module
refactor: extract stats handler to separate file
test: add portfolio edge case tests

Security

  • Secrets live in Cloudflare Workers secrets (runtime) and .env.deploy (local, gitignored). Never commit secrets.
  • .dev.vars is gitignored — local dev only.
  • grammY validates webhook secret on every update. No manual header parsing.
  • Module KV prefixing is a code-review boundary, not a cryptographic one.
  • Private commands are discoverability control, not access control.
  • HTML output in /help uses escapeHtml to prevent injection.