From e17d60b8acdaf28dfd0c7ce007bc230791167086 Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Wed, 13 May 2026 18:36:30 +0000 Subject: [PATCH] feat(navigation): update feature generator templates to emit wireUseCase Replace inline withSpan(withCapture(factory(deps))) form in the binder templates with wireUseCase({...}) calls so newly scaffolded features are consistent with the migrated production features. Also add assertFeatureConformance to bind-dev-seed.ts.hbs (aligns with the migrated auth/navigation pattern) and fix bind-dev-seed.test.ts.hbs to call binders with the ctx object form (BindContext) instead of the old two-argument (tracer, logger) form. Verified by running turbo gen feature testfeature and confirming: - Generated binders use wireUseCase for use cases - All 5 conformance gates pass on the scaffold - Scaffold cleaned up post-verification Co-Authored-By: Claude Sonnet 4.6 --- coverage/summary.json | 4 +- .../feature/src/di/bind-dev-seed.test.ts.hbs | 6 +-- .../feature/src/di/bind-dev-seed.ts.hbs | 52 ++++++++++++------- .../feature/src/di/bind-production.ts.hbs | 40 +++++++------- 4 files changed, 59 insertions(+), 43 deletions(-) diff --git a/coverage/summary.json b/coverage/summary.json index fbb89e4..6444615 100644 --- a/coverage/summary.json +++ b/coverage/summary.json @@ -1,6 +1,6 @@ { - "generatedAt": "2026-05-13T18:08:34.469Z", - "commit": "57e9d98", + "generatedAt": "2026-05-13T18:36:10.133Z", + "commit": "9800fed", "repo": { "statements": 95.86, "branches": 88.91, diff --git a/turbo/generators/templates/feature/src/di/bind-dev-seed.test.ts.hbs b/turbo/generators/templates/feature/src/di/bind-dev-seed.test.ts.hbs index bf69822..a982dbd 100644 --- a/turbo/generators/templates/feature/src/di/bind-dev-seed.test.ts.hbs +++ b/turbo/generators/templates/feature/src/di/bind-dev-seed.test.ts.hbs @@ -29,7 +29,7 @@ describe("bindDevSeed{{pascalCase name}}", () => { }); it("populates the repository with the dev seed", async () => { - await bindDevSeed{{pascalCase name}}(noop.tracer, noop.logger); + await bindDevSeed{{pascalCase name}}(noop); const repo = {{camelCase name}}Container.get( {{constantCase name}}_SYMBOLS.I{{pascalCase entity}}Repository, @@ -41,12 +41,12 @@ describe("bindDevSeed{{pascalCase name}}", () => { }); it("is idempotent — calling twice rebuilds a fresh populated repo", async () => { - await bindDevSeed{{pascalCase name}}(noop.tracer, noop.logger); + await bindDevSeed{{pascalCase name}}(noop); const before = {{camelCase name}}Container.get( {{constantCase name}}_SYMBOLS.I{{pascalCase entity}}Repository, ); - await bindDevSeed{{pascalCase name}}(noop.tracer, noop.logger); + await bindDevSeed{{pascalCase name}}(noop); const after = {{camelCase name}}Container.get( {{constantCase name}}_SYMBOLS.I{{pascalCase entity}}Repository, ); diff --git a/turbo/generators/templates/feature/src/di/bind-dev-seed.ts.hbs b/turbo/generators/templates/feature/src/di/bind-dev-seed.ts.hbs index 5960f49..142c201 100644 --- a/turbo/generators/templates/feature/src/di/bind-dev-seed.ts.hbs +++ b/turbo/generators/templates/feature/src/di/bind-dev-seed.ts.hbs @@ -6,6 +6,11 @@ import { type ILogger, } from "@repo/core-shared/instrumentation"; import type { BindContext } from "@repo/core-shared/di"; +import { + assertFeatureConformance, + wireUseCase, +} from "@repo/core-shared/conformance"; +import { {{camelCase name}}Manifest } from "../feature.manifest.js"; import { {{camelCase name}}Container } from "./container.js"; import { {{constantCase name}}_SYMBOLS } from "./symbols.js"; import { Mock{{pascalCase entity}}Repository } from "../infrastructure/repositories/{{kebabCase entity}}.repository.mock.js"; @@ -26,7 +31,6 @@ import type { I{{pascalCase entity}}Repository } from "../application/repositori */ export async function bindDevSeed{{pascalCase name}}(ctx: BindContext): Promise { const { tracer, logger, bus, queue, realtime, realtimeRegistry } = ctx; - void bus; void queue; void realtime; void realtimeRegistry; // Bind shared instrumentation into feature container if ({{camelCase name}}Container.isBound(INSTRUMENTATION_SYMBOLS.TRACER)) { @@ -46,27 +50,22 @@ export async function bindDevSeed{{pascalCase name}}(ctx: BindContext): Promise< .bind({{constantCase name}}_SYMBOLS.I{{pascalCase entity}}Repository) .toConstantValue(repo); - // Wrap use case + controller identically to bind-production - const wrappedGet{{pascalCase entity}} = withSpan( + // Use case + const wrappedGet{{pascalCase entity}} = wireUseCase({ + container: {{camelCase name}}Container, + symbol: {{constantCase name}}_SYMBOLS.IGet{{pascalCase entity}}UseCase, + factory: get{{pascalCase entity}}UseCase, + deps: [repo], + feature: "{{kebabCase name}}", + layer: "use-case", + name: "get{{pascalCase entity}}", tracer, - { name: "{{camelCase name}}.get{{pascalCase entity}}", op: "use-case" }, - withCapture( - logger, - { feature: "{{kebabCase name}}", layer: "use-case", name: "{{camelCase name}}.get{{pascalCase entity}}" }, - get{{pascalCase entity}}UseCase(repo), - ), - ); + logger, + }); - for (const sym of [ - {{constantCase name}}_SYMBOLS.IGet{{pascalCase entity}}UseCase, - {{constantCase name}}_SYMBOLS.IGet{{pascalCase entity}}Controller, - ]) { - if ({{camelCase name}}Container.isBound(sym)) {{camelCase name}}Container.unbind(sym); + if ({{camelCase name}}Container.isBound({{constantCase name}}_SYMBOLS.IGet{{pascalCase entity}}Controller)) { + {{camelCase name}}Container.unbind({{constantCase name}}_SYMBOLS.IGet{{pascalCase entity}}Controller); } - {{camelCase name}}Container - .bind({{constantCase name}}_SYMBOLS.IGet{{pascalCase entity}}UseCase) - .toConstantValue(wrappedGet{{pascalCase entity}}); - {{camelCase name}}Container .bind({{constantCase name}}_SYMBOLS.IGet{{pascalCase entity}}Controller) .toConstantValue( @@ -80,7 +79,22 @@ export async function bindDevSeed{{pascalCase name}}(ctx: BindContext): Promise< ), ), ); + // 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( + {{camelCase name}}Container, + {{camelCase name}}Manifest, + { + get{{pascalCase entity}}: {{constantCase name}}_SYMBOLS.IGet{{pascalCase entity}}UseCase, + }, + ctx, + ); } diff --git a/turbo/generators/templates/feature/src/di/bind-production.ts.hbs b/turbo/generators/templates/feature/src/di/bind-production.ts.hbs index 6832e06..7ee3eeb 100644 --- a/turbo/generators/templates/feature/src/di/bind-production.ts.hbs +++ b/turbo/generators/templates/feature/src/di/bind-production.ts.hbs @@ -6,17 +6,19 @@ import { type ILogger, } from "@repo/core-shared/instrumentation"; import type { BindProductionContext } from "@repo/core-shared/di"; +import { + assertFeatureConformance, + wireUseCase, +} from "@repo/core-shared/conformance"; import { {{camelCase name}}Container } from "./container"; import { {{constantCase name}}_SYMBOLS } from "./symbols"; +import { {{camelCase name}}Manifest } from "../feature.manifest"; import { {{pascalCase entity}}Repository } from "../infrastructure/repositories/{{kebabCase entity}}.repository"; import { get{{pascalCase entity}}UseCase } from "../application/use-cases/get-{{kebabCase entity}}.use-case"; import { get{{pascalCase entity}}Controller } from "../interface-adapters/controllers/get-{{kebabCase entity}}.controller"; -import { assertFeatureConformance } from "@repo/core-shared/conformance"; -import { {{camelCase name}}Manifest } from "../feature.manifest"; export function bindProduction{{pascalCase name}}(ctx: BindProductionContext): void { const { config, tracer, logger, bus, queue, realtime, realtimeRegistry } = ctx; - void bus; void queue; void realtime; void realtimeRegistry; // Bind shared instrumentation into feature container if ({{camelCase name}}Container.isBound(INSTRUMENTATION_SYMBOLS.TRACER)) { @@ -37,23 +39,18 @@ export function bindProduction{{pascalCase name}}(ctx: BindProductionContext): v .bind({{constantCase name}}_SYMBOLS.I{{pascalCase entity}}Repository) .toConstantValue(repo); - // Use case — wrapped with span + capture at bind time - const wrappedGet{{pascalCase entity}} = withSpan( + // Use case + const wrappedGet{{pascalCase entity}} = wireUseCase({ + container: {{camelCase name}}Container, + symbol: {{constantCase name}}_SYMBOLS.IGet{{pascalCase entity}}UseCase, + factory: get{{pascalCase entity}}UseCase, + deps: [repo], + feature: "{{kebabCase name}}", + layer: "use-case", + name: "get{{pascalCase entity}}", tracer, - { name: "{{camelCase name}}.get{{pascalCase entity}}", op: "use-case" }, - withCapture( - logger, - { feature: "{{kebabCase name}}", layer: "use-case", name: "{{camelCase name}}.get{{pascalCase entity}}" }, - get{{pascalCase entity}}UseCase(repo), - ), - ); - - if ({{camelCase name}}Container.isBound({{constantCase name}}_SYMBOLS.IGet{{pascalCase entity}}UseCase)) { - {{camelCase name}}Container.unbind({{constantCase name}}_SYMBOLS.IGet{{pascalCase entity}}UseCase); - } - {{camelCase name}}Container - .bind({{constantCase name}}_SYMBOLS.IGet{{pascalCase entity}}UseCase) - .toConstantValue(wrappedGet{{pascalCase entity}}); + logger, + }); // Controller — wrapped with span at bind time if ({{camelCase name}}Container.isBound({{constantCase name}}_SYMBOLS.IGet{{pascalCase entity}}Controller)) { @@ -72,6 +69,11 @@ export function bindProduction{{pascalCase name}}(ctx: BindProductionContext): v ), ), ); + // bus + queue are passed through; generated handlers consume them at the anchors below. + void bus; + void queue; + void realtime; + void realtimeRegistry; // // //