# DESIGN — F-002 Database foundation with module-owned schemas ## Affected modules - `src/infrastructure/db` (new): pool factory + migration runner entry. ## Modules touched - `src/infrastructure/db` - `project/migrations/` (new folder, SQL files) - `project/docker-compose.yml` (new) ## Modules NOT touched - All business modules. No HTTP API change. No shared/ change. ## New interfaces - `src/infrastructure/db/pool.ts`: `createPoolFromEnv(): Pool` (fail fast if `DATABASE_URL` missing), `query(text, params)` helper typed over pg. - npm scripts: `db:up`, `db:down`, `db:status`, `docker:up`, `docker:down`, `test:integration`. ## API changes - None. ## Database changes - Baseline migration `001_baseline.js` (node-pg-migrate, pgm.sql): - `CREATE EXTENSION IF NOT EXISTS citext;` - `CREATE EXTENSION IF NOT EXISTS pgcrypto;` - `CREATE TABLE app_meta (key text PRIMARY KEY, value text NOT NULL, updated_at timestamptz NOT NULL DEFAULT now());` - Down: drop table, drop extensions. - Migration version tracking table owned by the migration tool. ## Naming convention (documented rule) ```text _ e.g. catalog_products, inventory_stock, orders_orders _
_id_seq sequences owned by their table ``` - A module never queries tables without its own prefix. - Migrations are immutable once merged; fixes ship as new migrations. ## Events - None. ## External integrations - Docker Compose: postgres:16-alpine (port 5432), redis:7-alpine (port 6379), named volumes. ## Cache changes - None (Redis present but unused until cache ticket). ## Security considerations - Compose credentials are dev-only and public by design; documented as such. - `.env.example` committed, real `.env` gitignored. - No secrets in migration files. ## Toolchain decisions - Migration tool: **node-pg-migrate** (pure npm dependency, battle-tested, SQL-first via `pgm.sql`, supports up/down and dry-run). Rejected: hand-rolled runner (reinvention), Flyway/golang-migrate (foreign toolchains for a Node monolith). - Driver: **pg** (standard). - Env loading: Node 22 `--env-file` flag; no dotenv dependency. ## Test strategy - Integration tests (`*.itest.ts`) run only when `TEST_DATABASE_URL` is set: `describe.skipIf` — explicit, no magic. - They verify: fresh up creates schema; second up is no-op; down reverts cleanly; `app_meta` usable via pool helper. ## Migration strategy - Greenfield; compose starts empty volume, migrations run from zero. ## Rollback strategy - `db:down` reverts last migration; compose volumes can be removed with `docker compose down -v`.