- node-pg-migrate + pg: baseline migration (extensions, app_meta) with working down - src/infrastructure/db fail-fast pool and typed query helper - docker-compose: postgres:16-alpine + redis:7-alpine with one-command up - table naming convention <module>_<table> documented in README - integration tests (6) against real PostgreSQL; strict identifier validation for test DDL after security-gate hardening round - deps justified in spec/tech.md; all gates approved; verify.sh green
2.5 KiB
2.5 KiB
DESIGN — F-002 Database foundation with module-owned schemas
Affected modules
src/infrastructure/db(new): pool factory + migration runner entry.
Modules touched
src/infrastructure/dbproject/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 ifDATABASE_URLmissing),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)
<module>_<table> e.g. catalog_products, inventory_stock, orders_orders
<module>_<table>_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.examplecommitted, real.envgitignored.- 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-fileflag; no dotenv dependency.
Test strategy
- Integration tests (
*.itest.ts) run only whenTEST_DATABASE_URLis set:describe.skipIf— explicit, no magic. - They verify: fresh up creates schema; second up is no-op; down reverts cleanly;
app_metausable via pool helper.
Migration strategy
- Greenfield; compose starts empty volume, migrations run from zero.
Rollback strategy
db:downreverts last migration; compose volumes can be removed withdocker compose down -v.