- 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
64 lines
3.6 KiB
Markdown
64 lines
3.6 KiB
Markdown
# 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 <module>_<table> 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).
|