diff --git a/docs/architecture/data-flow-explainer.html b/docs/architecture/data-flow-explainer.html index 7b9691d..8c53af1 100644 --- a/docs/architecture/data-flow-explainer.html +++ b/docs/architecture/data-flow-explainer.html @@ -1664,7 +1664,7 @@ footer .colophon {
The BlogModule binds IArticlesRepository to MockArticlesRepository by default — useful at dev/test time. At app boot, bindProductionBlog(config, tracer, logger, bus, queue) unbinds the symbol and rebinds it to new ArticlesRepository(config, tracer, logger). Use cases and controllers don't notice — they get whatever the symbol currently resolves to.
The BlogModule binds IArticlesRepository to MockArticlesRepository by default — useful at dev/test time. At app boot, bindProductionBlog(ctx: BindProductionContext) unbinds the symbol and rebinds it to new ArticlesRepository(ctx.config, ctx.tracer, ctx.logger). Use cases and controllers don't notice — they get whatever the symbol currently resolves to. The ctx object is built once by the app aggregator and passed to all feature binders.
This is also why the boundary stays clean: features don't import core-cms; the app passes the Payload config in.
Called from each app's bootstrap (apps/web-next/src/server/bind-production.ts) with the resolved Payload config.
export function bindProductionBlog(config: SanitizedConfig): void { +Called from each app's bootstrap (
+apps/web-next/src/server/bind-production.ts) with thectxobject built once by the aggregator.BindProductionContextis imported from@repo/core-shared/di.export function bindProductionBlog(ctx: BindProductionContext): void { + const { config, tracer, logger, bus, queue, realtime, realtimeRegistry } = ctx; if (blogContainer.isBound(BLOG_SYMBOLS.IArticlesRepository)) { blogContainer.unbind(BLOG_SYMBOLS.IArticlesRepository); } @@ -1773,7 +1774,7 @@ footer .colophon {The mock is reached from two directions. Both are legitimate, neither is "the test version":
-
@@ -1786,7 +1787,7 @@ footer .colophon {- By the DI container at runtime.
+BlogModulebindsIArticlesRepositorytoMockArticlesRepositoryat module-load time. Anything resolving that symbol — use cases, controllers, tRPC procedures, the dev server — gets the mock untilbindProductionBlog(config, tracer, logger, bus, queue)swaps it for the real Payload-backed one. See §03.- By the DI container at runtime.
BlogModulebindsIArticlesRepositorytoMockArticlesRepositoryat module-load time. Anything resolving that symbol — use cases, controllers, tRPC procedures, the dev server — gets the mock untilbindProductionBlog(ctx: BindProductionContext)swaps it for the real Payload-backed one. See §03.- By tests, via direct construction. Unit tests skip the container entirely. They construct the mock with
new MockArticlesRepository()and pass it directly into the use-case factory function. Same class, different consumer — just a closure with a fake repo.show: the mock as DI binding (in module.ts)
-This is from
+packages/blog/src/di/module.ts— the very first binding in the module is the mock. Everything downstream (use cases, controllers) resolves through this default.bindProductionBlog(config, tracer, logger, bus, queue)later replaces only this one line at app boot — use case + controller bindings stay put.This is from
packages/blog/src/di/module.ts— the very first binding in the module is the mock. Everything downstream (use cases, controllers) resolves through this default.bindProductionBlog(ctx: BindProductionContext)later replaces only this one line at app boot — use case + controller bindings stay put.export const BlogModule = new ContainerModule((bind) => { // 1) Mock is the DEFAULT binding for the repo symbol. // Dev server, unit tests, storybook all resolve to this. @@ -2191,7 +2192,7 @@ footer .colophon {di/bind-production.tsProduction binder
-+
bindProduction<F>(config)— unbinds the mock, rebinds the real Payload-backed impl.
bindProduction<F>(ctx: BindProductionContext)— unbinds the mock, rebinds the real Payload-backed impl. Thectxarg carries required fields (tracer,logger,config) and optional cross-cutting deps (bus,queue,realtime,realtimeRegistry).@@ -1190,7 +1190,9 @@ footer .colophon { └─ Noop or Sentry binders ← bind to sharedContainer └─ resolveEventsAndJobs*() ← ADR-015 (env-driven bus + queue) └─ Payload-backed in prod, in-memory in dev-seed - └─ bindProductionX(config, tracer, logger, bus, queue) + └─ build ctx: BindProductionContext = { config, tracer, logger, bus, queue, realtime, realtimeRegistry } + └─ required: tracer, logger, config | optional: bus, queue, realtime, realtimeRegistry + └─ bindProductionX(ctx) ← single ctx object passed to each feature binder └─ feature container also binds TRACER + LOGGER └─ withSpan(withCapture(...)) at every use case + controller └─ // <gen:event-handlers> / // <gen:jobs> injection sitesPros
- Decouples Payload config from the feature package — boundary stays clean
diff --git a/docs/architecture/di-explainer.html b/docs/architecture/di-explainer.html index d6e0294..9631de9 100644 --- a/docs/architecture/di-explainer.html +++ b/docs/architecture/di-explainer.html @@ -756,7 +756,7 @@ footer .colophon {file 04 · prod swapbind-production.ts
Replaces the mock repository binding with a real Payload-backed one at app boot.
-Exports
+bindProductionBlog(config, tracer, logger, bus, queue). Function body:blogContainer.unbind(symbol)if already bound, then.bind(symbol).toConstantValue(new ArticlesRepository(config, tracer, logger)). Use cases and controllers are wrapped viawithSpan(withCapture(factory(deps)))at bind time so they inherit instrumentation without changing their factory bodies. Thebusandqueueparams come from the app'sresolveEventsAndJobs*step (ADR-015) and feed the// <gen:event-handlers>/// <gen:jobs>injection sites.Exports
bindProductionBlog(ctx: BindProductionContext). Destructures{ config, tracer, logger, bus, queue, realtime, realtimeRegistry }fromctx. Function body:blogContainer.unbind(symbol)if already bound, then.bind(symbol).toConstantValue(new ArticlesRepository(config, tracer, logger)). Use cases and controllers are wrapped viawithSpan(withCapture(factory(deps)))at bind time so they inherit instrumentation without changing their factory bodies. The optionalbusandqueuefields come from the app'sresolveEventsAndJobs*step (ADR-015) and feed the// <gen:event-handlers>/// <gen:jobs>injection sites.BindProductionContextis imported from@repo/core-shared/di— features never import the optional packages directly for the binder signature.When it runs: called from app boot (apps/web-next/src/server/bind-production.ts) whenUSE_DEV_SEED ≠ "true"AND Payload config is resolvable.