Files
mercadodevida/work/artifacts/F-002/implementer.md
rikrdo 425fedd13e feat(F-002): database foundation with migrations and dev compose
- 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
2026-08-14 22:00:16 +02:00

3.6 KiB

Implementer — F-002 Database foundation with module-owned schemas

done -> work/artifacts/F-002/implementer.md

Files created (all under project/ unless noted)

  • docker-compose.yml (postgres:16-alpine + redis:7-alpine, named volumes, healthchecks, dev-only creds)
  • .env.example
  • migrations/001_baseline.js (node-pg-migrate; citext + pgcrypto extensions, app_meta table, with down)
  • src/infrastructure/db/pool.ts (createPoolFromEnv fail-fast, typed query helper)
  • src/infrastructure/db/tests/db-test-support.ts (recreateDatabase, runMigrations, tableExists helpers; strict identifier validation for DDL-interpolated db name)
  • src/infrastructure/db/tests/db-test-support.test.ts (pure helper tests incl. identifier rejection)
  • src/infrastructure/db/tests/migrations.itest.ts
  • src/infrastructure/db/tests/pool.itest.ts
  • scripts/db-status.mjs

Files modified

  • package.json: deps (pg, node-pg-migrate; @types/pg dev) + scripts (test:integration, docker:up/down, db:up/down/status)
  • vitest.config.ts: include *.itest.ts; fileParallelism false (integration files share one test DB)
  • .gitignore: .env
  • README.md: Database section + table naming convention
  • spec/tech.md: dependency justification rows for pg / node-pg-migrate / @types/pg
  • specs/F-002-database-foundation/DESIGN.md + TASKS.md: migration filename corrected to .js (node-pg-migrate native format)

Database migrations

  • 001_baseline: extensions citext + pgcrypto, table app_meta(key, value, updated_at). Down fully reverts.
  • Tracking table: pgmigrations (owned by node-pg-migrate).

API changes

  • None.

Tests added

  • migrations.itest.ts: fresh up creates schema / second up is no-op / down rolls back cleanly
  • pool.itest.ts: SELECT 1 roundtrip / app_meta insert-read-delete via query helper / fail fast without DATABASE_URL

Tests passed (evidence)

npm run docker:up        -> mdv-dev-postgres Healthy, mdv-dev-redis Healthy
npm run db:up (1st)      -> MIGRATION 001_baseline (UP), Migrations complete!
npm run db:up (2nd)      -> No migrations to run! (no-op)
npm run db:status        -> 001_baseline listed as applied
npm run db:down          -> MIGRATION 001_baseline (DOWN); psql to_regclass('public.app_meta') -> empty
npm run db:up (again)    -> re-applies cleanly
npm run lint             -> OK
npm run lint:boundaries  -> Boundary check OK: 11 file(s) checked
npm run typecheck        -> exit 0
npm run build            -> exit 0
npm test                 -> 4 files passed, 2 integration files skipped explicitly without TEST_DATABASE_URL (11 passed | 6 skipped)
npm run test:integration -> Test Files 2 passed (2), Tests 6 passed (6) against real PostgreSQL 16
docker exec mdv-dev-redis redis-cli ping -> PONG

Known limitations

  • node-pg-migrate programmatic runner requires explicit migrationsTable (passed 'pgmigrations' in the test helper to match CLI default).
  • Integration tests drop/create the shared test database; vitest fileParallelism disabled to avoid the race (documented in vitest.config.ts).
  • No connection retry/backoff yet; not needed while only dev scripts connect.

Follow-up work

  • F-004 will centralize env/config (current fail-fast reader stays until then).
  • First real module tables arrive with F-005+ following _ convention.

    Security hardening round (requested by security gate)

    • db-test-support.ts now validates the database name from TEST_DATABASE_URL against /^[a-zA-Z_][a-zA-Z0-9_]*$/ before interpolating it into DROP/CREATE DATABASE DDL; rejection covered by db-test-support.test.ts.
    • Re-verified: lint, typecheck, npm test (11 passed), test:integration (6 passed).