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:
@@ -1,8 +1,8 @@
|
|||||||
import { appRouter } from "@repo/core-api";
|
import { appRouter } from "@repo/core-api";
|
||||||
import { bindAllProduction } from "../../server/bind-production";
|
import { bindAll } from "../../server/bind-production";
|
||||||
|
|
||||||
export default async function AboutPage() {
|
export default async function AboutPage() {
|
||||||
await bindAllProduction();
|
await bindAll();
|
||||||
const caller = appRouter.createCaller({});
|
const caller = appRouter.createCaller({});
|
||||||
const page = await caller.marketingPages.pageBySlug({ slug: "about" });
|
const page = await caller.marketingPages.pageBySlug({ slug: "about" });
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
||||||
import { appRouter } from "@repo/core-api";
|
import { appRouter } from "@repo/core-api";
|
||||||
import { bindAllProduction } from "../../../../server/bind-production";
|
import { bindAll } from "../../../../server/bind-production";
|
||||||
|
|
||||||
const handler = async (req: Request) => {
|
const handler = async (req: Request) => {
|
||||||
await bindAllProduction();
|
await bindAll();
|
||||||
return fetchRequestHandler({
|
return fetchRequestHandler({
|
||||||
endpoint: "/api/trpc",
|
endpoint: "/api/trpc",
|
||||||
req,
|
req,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { appRouter } from "@repo/core-api";
|
import { appRouter } from "@repo/core-api";
|
||||||
import { bindAllProduction } from "../../../server/bind-production";
|
import { bindAll } from "../../../server/bind-production";
|
||||||
|
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
params: Promise<{ slug: string }>;
|
params: Promise<{ slug: string }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function BlogPostPage({ params }: PageProps) {
|
export default async function BlogPostPage({ params }: PageProps) {
|
||||||
await bindAllProduction();
|
await bindAll();
|
||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const caller = appRouter.createCaller({});
|
const caller = appRouter.createCaller({});
|
||||||
const article = await caller.blog.articleBySlug({ slug });
|
const article = await caller.blog.articleBySlug({ slug });
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { appRouter } from "@repo/core-api";
|
import { appRouter } from "@repo/core-api";
|
||||||
import { bindAllProduction } from "../server/bind-production";
|
import { bindAll } from "../server/bind-production";
|
||||||
|
|
||||||
export default async function Home() {
|
export default async function Home() {
|
||||||
await bindAllProduction();
|
await bindAll();
|
||||||
const caller = appRouter.createCaller({});
|
const caller = appRouter.createCaller({});
|
||||||
|
|
||||||
const [siteSettings, header, articles] = await Promise.all([
|
const [siteSettings, header, articles] = await Promise.all([
|
||||||
|
|||||||
@@ -5,9 +5,18 @@ import { bindProductionAuth } from "@repo/auth/di/bind-production";
|
|||||||
import { bindProductionMarketingPages } from "@repo/marketing-pages/di/bind-production";
|
import { bindProductionMarketingPages } from "@repo/marketing-pages/di/bind-production";
|
||||||
import { bindProductionNavigation } from "@repo/navigation/di/bind-production";
|
import { bindProductionNavigation } from "@repo/navigation/di/bind-production";
|
||||||
import { bindProductionMedia } from "@repo/media/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;
|
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> {
|
export async function bindAllProduction(): Promise<void> {
|
||||||
if (bound) return;
|
if (bound) return;
|
||||||
bound = true;
|
bound = true;
|
||||||
@@ -18,3 +27,35 @@ export async function bindAllProduction(): Promise<void> {
|
|||||||
bindProductionNavigation(resolvedConfig);
|
bindProductionNavigation(resolvedConfig);
|
||||||
bindProductionMedia(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();
|
||||||
|
}
|
||||||
|
|||||||
1270
docs/architecture/di-explainer.html
Normal file
1270
docs/architecture/di-explainer.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user