feat(app): bindAll() Rule 0 — DSN-driven instrumentation (orthogonal to repo mode)
Introduces resolveInstrumentation() in apps/web-next/src/server/bind-production.ts: reads WEB_NEXT_SENTRY_DSN; if set, calls bindSentryInstrumentation; else Noop. Both bindAllProduction and bindAllDevSeed call resolveInstrumentation() at the top. Exports __resetBindStateForTests() and __getInstrumentationForTests() for test helpers. Adds inversify + reflect-metadata as direct dependencies of web-next. Existing 6 tests still pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,10 +25,12 @@
|
||||
"@repo/navigation": "workspace:*",
|
||||
"@tanstack/react-query": "^5.66.0",
|
||||
"@trpc/server": "^11.0.0",
|
||||
"inversify": "^6.2.0",
|
||||
"next": "^15.3.0",
|
||||
"payload": "^3.14.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"superjson": "^2.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
// apps/web-next/src/server/bind-production.ts
|
||||
// SERVER-ONLY: this module imports Payload config and must never be bundled into the browser.
|
||||
import "reflect-metadata";
|
||||
import { Container } from "inversify";
|
||||
import config from "@repo/core-cms";
|
||||
import {
|
||||
bindNoopInstrumentation,
|
||||
bindSentryInstrumentation,
|
||||
type ITracer,
|
||||
type ILogger,
|
||||
} from "@repo/core-shared/instrumentation";
|
||||
import { bindProductionBlog } from "@repo/blog/di/bind-production";
|
||||
import { bindProductionAuth } from "@repo/auth/di/bind-production";
|
||||
import { bindProductionMarketingPages } from "@repo/marketing-pages/di/bind-production";
|
||||
@@ -13,13 +22,39 @@ import { bindDevSeedMedia } from "@repo/media/di/bind-dev-seed";
|
||||
|
||||
let bound = false;
|
||||
|
||||
// Shared container holds TRACER + LOGGER bindings; per-feature containers
|
||||
// receive references via parameter passing. This separates the instrumentation
|
||||
// container (one) from feature containers (per-feature, ADR-008).
|
||||
const sharedContainer = new Container();
|
||||
|
||||
let resolvedTracer: ITracer | null = null;
|
||||
let resolvedLogger: ILogger | null = null;
|
||||
|
||||
/** Rule 0: pick instrumentation backend from DSN env (orthogonal to repo mode). */
|
||||
function resolveInstrumentation(): { tracer: ITracer; logger: ILogger } {
|
||||
if (resolvedTracer && resolvedLogger) {
|
||||
return { tracer: resolvedTracer, logger: resolvedLogger };
|
||||
}
|
||||
const dsn = process.env.WEB_NEXT_SENTRY_DSN;
|
||||
const result = dsn
|
||||
? bindSentryInstrumentation(sharedContainer, { dsn, app: "web-next" })
|
||||
: bindNoopInstrumentation(sharedContainer);
|
||||
resolvedTracer = result.tracer;
|
||||
resolvedLogger = result.logger;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Production path: swap each feature's mock repository binding for the real
|
||||
* Payload-backed one. Constructs `new XRepository(config)` per feature.
|
||||
* Payload-backed one. Constructs `new XRepository(config, tracer, logger)` per
|
||||
* feature once Phase E feature wiring lands. Until Phase E, this still calls
|
||||
* the existing `bindProductionX(config)` signature; the unused `tracer`/`logger`
|
||||
* are forwarded by Phase E commits as those binders are updated.
|
||||
*/
|
||||
export async function bindAllProduction(): Promise<void> {
|
||||
if (bound) return;
|
||||
bound = true;
|
||||
resolveInstrumentation(); // Rule 0
|
||||
const resolvedConfig = await config;
|
||||
bindProductionAuth(resolvedConfig);
|
||||
bindProductionBlog(resolvedConfig);
|
||||
@@ -31,13 +66,12 @@ export async function bindAllProduction(): Promise<void> {
|
||||
/**
|
||||
* 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.
|
||||
* Payload booted. Mutually exclusive with `bindAllProduction()`.
|
||||
*/
|
||||
export async function bindAllDevSeed(): Promise<void> {
|
||||
if (bound) return;
|
||||
bound = true;
|
||||
resolveInstrumentation(); // Rule 0
|
||||
await bindDevSeedAuth();
|
||||
await bindDevSeedBlog();
|
||||
await bindDevSeedMarketingPages();
|
||||
@@ -50,19 +84,12 @@ export async function bindAllDevSeed(): Promise<void> {
|
||||
*
|
||||
* Resolution order (first match wins):
|
||||
*
|
||||
* 1. `USE_DEV_SEED === "true"` → dev seed (explicit override; works in
|
||||
* any NODE_ENV — useful for staging,
|
||||
* storybook, design review).
|
||||
* 2. `NODE_ENV === "production"` → real Payload via bindAllProduction(config).
|
||||
* 3. otherwise → dev seed (developer-friendly default;
|
||||
* `pnpm dev` works without Payload booted).
|
||||
*
|
||||
* To force production locally without changing NODE_ENV, set
|
||||
* `USE_DEV_SEED=false` is NOT enough — set `NODE_ENV=production` instead.
|
||||
*
|
||||
* Idempotent: guarded by the same `bound` flag the underlying binders share.
|
||||
* Call from server entry points (route handlers, page server components,
|
||||
* tRPC route handlers) before resolving any feature controller.
|
||||
* Rule 0 (always): instrumentation (Noop vs Sentry) from WEB_NEXT_SENTRY_DSN
|
||||
* presence — runs inside both bindAllProduction and
|
||||
* bindAllDevSeed via resolveInstrumentation().
|
||||
* Rule 1: USE_DEV_SEED === "true" → dev seed (explicit override)
|
||||
* Rule 2: NODE_ENV === "production" → real Payload via bindAllProduction
|
||||
* Rule 3: otherwise → dev seed (developer-friendly default)
|
||||
*/
|
||||
export async function bindAll(): Promise<void> {
|
||||
if (process.env.USE_DEV_SEED === "true") {
|
||||
@@ -73,7 +100,20 @@ export async function bindAll(): Promise<void> {
|
||||
await bindAllProduction();
|
||||
return;
|
||||
}
|
||||
// Default for dev / test / unset NODE_ENV: dev seed so the app boots
|
||||
// without Payload running.
|
||||
await bindAllDevSeed();
|
||||
}
|
||||
|
||||
/** Test-only resets — not exported via package. Used by bind-production.test.ts. */
|
||||
export function __resetBindStateForTests(): void {
|
||||
bound = false;
|
||||
resolvedTracer = null;
|
||||
resolvedLogger = null;
|
||||
}
|
||||
|
||||
/** Test-only accessor for resolved instrumentation. */
|
||||
export function __getInstrumentationForTests(): {
|
||||
tracer: ITracer | null;
|
||||
logger: ILogger | null;
|
||||
} {
|
||||
return { tracer: resolvedTracer, logger: resolvedLogger };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user