import { withSpan, withCapture, INSTRUMENTATION_SYMBOLS, type ITracer, type ILogger, } from "@repo/core-shared/instrumentation"; import type { BindContext } from "@repo/core-shared/di"; import { assertFeatureConformance, wireUseCase, } from "@repo/core-shared/conformance"; import { blogManifest } from "../feature.manifest"; import { blogContainer } from "./container"; import { BLOG_SYMBOLS } from "./symbols"; import { MockArticlesRepository } from "../infrastructure/repositories/articles.repository.mock"; import { buildDevArticles } from "../__seeds__/dev"; import { getArticlesUseCase } from "../application/use-cases/get-articles.use-case"; import { getArticleBySlugUseCase } from "../application/use-cases/get-article-by-slug.use-case"; import { createArticleUseCase } from "../application/use-cases/create-article.use-case"; import { getArticlesController } from "../interface-adapters/controllers/get-articles.controller"; import { getArticleBySlugController } from "../interface-adapters/controllers/get-article-by-slug.controller"; import { createArticleController } from "../interface-adapters/controllers/create-article.controller"; import type { IArticlesRepository } from "../application/repositories/articles.repository.interface"; /** * Replace the default empty mock with a populated one for dev mode + storybook. * * Call this from app boot when `USE_DEV_SEED=true`, mutually exclusive with * `bindProductionBlog(config)`. Tests must NOT call this — they construct * `new MockArticlesRepository()` directly and seed via factories per-test. * * Idempotent: safe to call multiple times; each call rebuilds a fresh * populated repo and rebinds the symbol. */ export async function bindDevSeedBlog(ctx: BindContext): Promise { const { tracer, logger, bus, queue, realtime, realtimeRegistry } = ctx; // Bind shared instrumentation into feature container if (blogContainer.isBound(INSTRUMENTATION_SYMBOLS.TRACER)) { blogContainer.unbind(INSTRUMENTATION_SYMBOLS.TRACER); } if (blogContainer.isBound(INSTRUMENTATION_SYMBOLS.LOGGER)) { blogContainer.unbind(INSTRUMENTATION_SYMBOLS.LOGGER); } blogContainer .bind(INSTRUMENTATION_SYMBOLS.TRACER) .toConstantValue(tracer); blogContainer .bind(INSTRUMENTATION_SYMBOLS.LOGGER) .toConstantValue(logger); if (blogContainer.isBound(BLOG_SYMBOLS.IArticlesRepository)) { blogContainer.unbind(BLOG_SYMBOLS.IArticlesRepository); } const repo = new MockArticlesRepository(tracer, logger); for (const article of buildDevArticles()) { await repo.createArticle(article); } blogContainer .bind(BLOG_SYMBOLS.IArticlesRepository) .toConstantValue(repo); // Use cases const wrappedGetArticles = wireUseCase({ container: blogContainer, symbol: BLOG_SYMBOLS.IGetArticlesUseCase, factory: getArticlesUseCase, deps: [repo], feature: "blog", layer: "use-case", name: "getArticles", tracer, logger, }); const wrappedGetArticleBySlug = wireUseCase({ container: blogContainer, symbol: BLOG_SYMBOLS.IGetArticleBySlugUseCase, factory: getArticleBySlugUseCase, deps: [repo], feature: "blog", layer: "use-case", name: "getArticleBySlug", tracer, logger, }); const wrappedCreateArticle = wireUseCase({ container: blogContainer, symbol: BLOG_SYMBOLS.ICreateArticleUseCase, factory: createArticleUseCase, deps: [repo], feature: "blog", layer: "use-case", name: "createArticle", tracer, logger, }); for (const sym of [ BLOG_SYMBOLS.IGetArticlesController, BLOG_SYMBOLS.IGetArticleBySlugController, BLOG_SYMBOLS.ICreateArticleController, ]) { if (blogContainer.isBound(sym)) blogContainer.unbind(sym); } blogContainer .bind(BLOG_SYMBOLS.IGetArticlesController) .toConstantValue( withSpan( tracer, { name: "blog.getArticles", op: "controller" }, withCapture( logger, { feature: "blog", layer: "controller", name: "blog.getArticles" }, getArticlesController(wrappedGetArticles), ), ), ); blogContainer.bind(BLOG_SYMBOLS.IGetArticleBySlugController).toConstantValue( withSpan( tracer, { name: "blog.getArticleBySlug", op: "controller" }, withCapture( logger, { feature: "blog", layer: "controller", name: "blog.getArticleBySlug", }, getArticleBySlugController(wrappedGetArticleBySlug), ), ), ); blogContainer .bind(BLOG_SYMBOLS.ICreateArticleController) .toConstantValue( withSpan( tracer, { name: "blog.createArticle", op: "controller" }, withCapture( logger, { feature: "blog", layer: "controller", name: "blog.createArticle" }, createArticleController(wrappedCreateArticle), ), ), ); // bus + queue are passed through; generated handlers consume them at the anchors below. void bus; void queue; void realtime; void realtimeRegistry; // // // // Boot-time conformance check (dev-seed mode). assertFeatureConformance( blogContainer, blogManifest, { getArticles: BLOG_SYMBOLS.IGetArticlesUseCase, getArticleBySlug: BLOG_SYMBOLS.IGetArticleBySlugUseCase, createArticle: BLOG_SYMBOLS.ICreateArticleUseCase, }, ctx, ); }