feat(app): wire bindAll() + bindAllDevSeed() dispatcher; add DI explainer page

apps/web-next/src/server/bind-production.ts now exports three functions:
- bindAllProduction() — production-only binders
- bindAllDevSeed() — dev-seed-only binders (NEW, calls all 5 features)
- bindAll() — dispatcher that branches on USE_DEV_SEED env var

All page/route callers (page.tsx, about/page.tsx, blog/[slug]/page.tsx,
api/trpc/[trpc]/route.ts) updated from bindAllProduction → bindAll so the
env flag actually has effect.

docs/architecture/di-explainer.html (NEW): standalone interactive page
explaining the di/ folder file-by-file, the loading sequence (8 stages),
the three binding kinds (.to / .toDynamicValue / .toConstantValue), an
interactive three-mode picker showing how the same blogContainer state
differs across default/dev-seed/production, a conditions table, and a
final card on how tests bypass DI entirely. Sister page to
data-flow-explainer.html.

Refactor log entry + canonical doc updates follow in subsequent commits.
This commit is contained in:
2026-05-06 19:10:29 +02:00
parent 10479c4d55
commit 68e934c0a5
6 changed files with 1319 additions and 8 deletions

View File

@@ -5,9 +5,18 @@ import { bindProductionAuth } from "@repo/auth/di/bind-production";
import { bindProductionMarketingPages } from "@repo/marketing-pages/di/bind-production";
import { bindProductionNavigation } from "@repo/navigation/di/bind-production";
import { bindProductionMedia } from "@repo/media/di/bind-production";
import { bindDevSeedBlog } from "@repo/blog/di/bind-dev-seed";
import { bindDevSeedAuth } from "@repo/auth/di/bind-dev-seed";
import { bindDevSeedMarketingPages } from "@repo/marketing-pages/di/bind-dev-seed";
import { bindDevSeedNavigation } from "@repo/navigation/di/bind-dev-seed";
import { bindDevSeedMedia } from "@repo/media/di/bind-dev-seed";
let bound = false;
/**
* Production path: swap each feature's mock repository binding for the real
* Payload-backed one. Constructs `new XRepository(config)` per feature.
*/
export async function bindAllProduction(): Promise<void> {
if (bound) return;
bound = true;
@@ -18,3 +27,35 @@ export async function bindAllProduction(): Promise<void> {
bindProductionNavigation(resolvedConfig);
bindProductionMedia(resolvedConfig);
}
/**
* Dev-seed path: keep each feature's MockXRepository in place but populate it
* with realistic seed data so the running app shows non-empty UI without
* Payload booted. Useful for storybook, design review, and offline dev.
*
* Mutually exclusive with `bindAllProduction()` — both rebind the same symbols.
*/
export async function bindAllDevSeed(): Promise<void> {
if (bound) return;
bound = true;
await bindDevSeedAuth();
await bindDevSeedBlog();
await bindDevSeedMarketingPages();
await bindDevSeedNavigation();
await bindDevSeedMedia();
}
/**
* Boot dispatcher: pick the binder based on `process.env.USE_DEV_SEED`.
* Idempotent — guarded by the same `bound` flag as `bindAllProduction`.
*
* Call this from server entry points (route handlers, page server components)
* before resolving any feature controller.
*/
export async function bindAll(): Promise<void> {
if (process.env.USE_DEV_SEED === "true") {
await bindAllDevSeed();
return;
}
await bindAllProduction();
}