Phase 0 — NestJS + Zod foundation harness (F1–F8) (#1050)

Co-hosted NestJS app behind the existing Express server via a strangler-fig dispatcher, sharing the same better-sqlite3 connection and JWT httpOnly cookie. Additive and dormant: default routing stays on Express, Nest only serves its own /api/_nest diagnostics until a module opts in.

F1 @trek/shared Zod contract package; F2 Nest bootstrap co-hosted (fall-through, single Dockerfile/port); F3 shared better-sqlite3 provider; F4 JWT cookie auth guard (+ @CurrentUser, admin guard); F5 Zod validation pipe + error-envelope parity; F6 Nest test + coverage gates; F7 per-prefix strangler toggle (env, default Express); F8 CI build/typecheck/test/coverage.

Remaining F4/F6/F8 checklist items (trip-access + permission levels + MFA policy, e2e harness/seed + 80% gate, Nest↔Express parity test, Playwright PR-comment workflow) are tracked on the first consuming module cards (L1/A1/C1).
This commit is contained in:
Maurice
2026-05-25 14:29:30 +02:00
committed by GitHub
parent e27be5c965
commit 0b218d53b2
43 changed files with 3790 additions and 176 deletions
+24
View File
@@ -0,0 +1,24 @@
/**
* Strangler toggle for the incremental NestJS migration.
*
* `getNestPrefixes()` returns the request path prefixes that NestJS handles;
* every other path falls through to the legacy Express app. The default is the
* set of prefixes whose Nest modules exist. Operators can override it at runtime
* via the `NEST_PREFIXES` env var (comma-separated) for instant Nest<->Express
* rollback — no redeploy, no code change. Setting `NEST_PREFIXES=` (empty) routes
* everything back to the legacy app.
*/
const DEFAULT_NEST_PREFIXES = ['/api/_nest'];
export function getNestPrefixes(): string[] {
const raw = process.env.NEST_PREFIXES;
if (raw !== undefined) {
return raw.split(',').map((s) => s.trim()).filter(Boolean);
}
return DEFAULT_NEST_PREFIXES;
}
/** Builds a matcher: true when `path` belongs to one of the migrated prefixes. */
export function makeNestPathMatcher(prefixes: string[]): (path: string) => boolean {
return (path) => prefixes.some((prefix) => path === prefix || path.startsWith(prefix + '/'));
}