From ca2e7d8c1053a2ead4b4ddf6df69f79bb99bf33a Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Sat, 9 May 2026 13:02:36 +0200 Subject: [PATCH] fix: address Phase 1 spec review findings - di-explainer.html: update stale JS data strings from positional-arg form (bindAllProduction(config), bindProductionBlog(config,tracer,...)) to ctx-arg form (bindAllProduction(deps), bindProductionBlog(ctx)) with a note on ctx shape. - auth/sign-up.use-case: change bus param from IEventBus to EventBusProtocol|undefined; guard bus.publish with if(bus) so the use case is safe when core-events is absent (Phase 3+). - auth/bind-production + bind-dev-seed: drop as IEventBus cast and unused IEventBus import; ctx.bus is now passed directly (typed as EventBusProtocol|undefined). - marketing-pages/bind-production + bind-dev-seed: drop as IJobQueue cast and unused IJobQueue import; wrap event-handler DI block in if(queue) guard so the handler is only bound when core-jobs is wired. - core-shared/src/index.ts: add `export * from "./di"` as the plan specified (subpath export alone is no longer the only access path). Co-Authored-By: Claude Sonnet 4.6 --- docs/architecture/di-explainer.html | 4 +- .../application/use-cases/sign-up.use-case.ts | 17 +++++---- packages/auth/src/di/bind-dev-seed.ts | 3 +- packages/auth/src/di/bind-production.ts | 3 +- packages/core-shared/src/index.ts | 1 + .../marketing-pages/src/di/bind-dev-seed.ts | 38 ++++++++++--------- .../marketing-pages/src/di/bind-production.ts | 38 ++++++++++--------- 7 files changed, 55 insertions(+), 49 deletions(-) diff --git a/docs/architecture/di-explainer.html b/docs/architecture/di-explainer.html index 9631de9..3479209 100644 --- a/docs/architecture/di-explainer.html +++ b/docs/architecture/di-explainer.html @@ -1300,11 +1300,11 @@ const MODES = { ], }, prod: { - flag: 'NODE_ENV="production", USE_DEV_SEED unset · bindAllProduction(config) ran', + flag: 'NODE_ENV="production", USE_DEV_SEED unset · bindAllProduction(deps) ran', scenarioTag: 'when this happens', title: 'Production binder ran at app boot', narrative: [ - "App's bindAll() did not see USE_DEV_SEED=true, so it dispatched to bindAllProduction(). That resolved instrumentation (Rule 0), resolved a Payload-backed bus + queue (ADR-015), awaited the Payload config, and called bindProductionBlog(config, tracer, logger, bus, queue).", + "App's bindAll() did not see USE_DEV_SEED=true, so it dispatched to bindAllProduction(). That resolved instrumentation (Rule 0), resolved a Payload-backed bus + queue (ADR-015), awaited the Payload config, and called bindProductionBlog(ctx) where ctx carries { config, tracer, logger, bus?, queue?, realtime?, realtimeRegistry? }.", "The binder unbound IArticlesRepository and rebound it with .toConstantValue(new ArticlesRepository(config, tracer, logger)) — the real Payload-backed implementation that will hit Postgres on every method call.", "Use cases and controllers still resolve through their .toDynamicValue closures, but the closures now fetch the real repo. Callers cannot tell the difference — same interface, different storage." ], diff --git a/packages/auth/src/application/use-cases/sign-up.use-case.ts b/packages/auth/src/application/use-cases/sign-up.use-case.ts index 54bf9fe..c40c999 100644 --- a/packages/auth/src/application/use-cases/sign-up.use-case.ts +++ b/packages/auth/src/application/use-cases/sign-up.use-case.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import type { IEventBus } from "@repo/core-events"; +import type { EventBusProtocol } from "@repo/core-shared/di"; import { userSignedUpEvent } from "../../events/user-signed-up.event"; import { AuthenticationError } from "../../entities/errors/auth"; import { cookieSchema } from "../../entities/models/cookie"; @@ -36,7 +36,7 @@ export const signUpUseCase = ( usersRepository: IUsersRepository, authenticationService: IAuthenticationService, - bus: IEventBus, + bus: EventBusProtocol | undefined, ) => async (input: SignUpInput): Promise => { const existingUser = await usersRepository.getUserByUsername(input.username); @@ -57,11 +57,14 @@ export const signUpUseCase = // Auth is username-based — synthesize a deterministic email so the event // payload validates against userSignedUpEventSchema.email(). - await bus.publish(userSignedUpEvent, { - userId: newUser.id, - email: `${newUser.username}@example.local`, - signedUpAt: new Date().toISOString(), - }); + // bus is optional: absent when core-events is not wired (Phase 3+). + if (bus) { + await bus.publish(userSignedUpEvent, { + userId: newUser.id, + email: `${newUser.username}@example.local`, + signedUpAt: new Date().toISOString(), + }); + } return signUpOutputSchema.parse({ session, cookie }); }; diff --git a/packages/auth/src/di/bind-dev-seed.ts b/packages/auth/src/di/bind-dev-seed.ts index 3bbacb8..e0ee6d4 100644 --- a/packages/auth/src/di/bind-dev-seed.ts +++ b/packages/auth/src/di/bind-dev-seed.ts @@ -6,7 +6,6 @@ import { type ILogger, } from "@repo/core-shared/instrumentation"; import type { BindContext } from "@repo/core-shared/di"; -import type { IEventBus } from "@repo/core-events"; import { authContainer } from "./container.js"; import { AUTH_SYMBOLS } from "./symbols.js"; import { MockUsersRepository } from "../infrastructure/repositories/users.repository.mock.js"; @@ -78,7 +77,7 @@ export async function bindDevSeedAuth(ctx: BindContext): Promise { withCapture( logger, { feature: "auth", layer: "use-case", name: "auth.signUp" }, - signUpUseCase(repo, authService, bus as IEventBus), + signUpUseCase(repo, authService, bus), ), ); const wrappedSignOut = withSpan( diff --git a/packages/auth/src/di/bind-production.ts b/packages/auth/src/di/bind-production.ts index 6236167..2af12a1 100644 --- a/packages/auth/src/di/bind-production.ts +++ b/packages/auth/src/di/bind-production.ts @@ -6,7 +6,6 @@ import { type ILogger, } from "@repo/core-shared/instrumentation"; import type { BindProductionContext } from "@repo/core-shared/di"; -import type { IEventBus } from "@repo/core-events"; import { authContainer } from "./container"; import { AUTH_SYMBOLS } from "./symbols"; import { UsersRepository } from "../infrastructure/repositories/users.repository"; @@ -69,7 +68,7 @@ export function bindProductionAuth(ctx: BindProductionContext): void { withCapture( logger, { feature: "auth", layer: "use-case", name: "auth.signUp" }, - signUpUseCase(repo, authService, bus as IEventBus), + signUpUseCase(repo, authService, bus), ), ); const wrappedSignOut = withSpan( diff --git a/packages/core-shared/src/index.ts b/packages/core-shared/src/index.ts index 0c98e6c..261b0c4 100644 --- a/packages/core-shared/src/index.ts +++ b/packages/core-shared/src/index.ts @@ -1,3 +1,4 @@ export { requireEnv } from "./lib/env"; export { toIsoString } from "./lib/date"; +export * from "./di"; export * from "./instrumentation/index"; diff --git a/packages/marketing-pages/src/di/bind-dev-seed.ts b/packages/marketing-pages/src/di/bind-dev-seed.ts index fc3c69d..5fb97bb 100644 --- a/packages/marketing-pages/src/di/bind-dev-seed.ts +++ b/packages/marketing-pages/src/di/bind-dev-seed.ts @@ -6,7 +6,6 @@ import { type ILogger, } from "@repo/core-shared/instrumentation"; import type { BindContext } from "@repo/core-shared/di"; -import type { IJobQueue } from "@repo/core-shared/jobs"; import { marketingPagesContainer } from "./container.js"; import { MARKETING_PAGES_SYMBOLS } from "./symbols.js"; import { MockPagesRepository } from "../infrastructure/repositories/pages.repository.mock.js"; @@ -140,24 +139,27 @@ export async function bindDevSeedMarketingPages(ctx: BindContext): Promise // // onAuthUserSignedUpHandler subscription — generated, edit the handler file (not this block) for behavior. - const wrappedAuthUserSignedUp = withSpan( - tracer, - { name: "marketing-pages.onAuthUserSignedUpHandler", op: "event-handler" }, - withCapture( - logger, - { - feature: "marketing-pages", - layer: "event-handler", - name: "marketing-pages.onAuthUserSignedUpHandler", - }, - onAuthUserSignedUpHandler(queue as IJobQueue), - ), - ); - if (marketingPagesContainer.isBound(MARKETING_PAGES_SYMBOLS.IOnAuthUserSignedUpHandler)) { - marketingPagesContainer.unbind(MARKETING_PAGES_SYMBOLS.IOnAuthUserSignedUpHandler); + // queue is optional: guard so the handler is only bound when core-jobs is wired (Phase 3+). + if (queue) { + const wrappedAuthUserSignedUp = withSpan( + tracer, + { name: "marketing-pages.onAuthUserSignedUpHandler", op: "event-handler" }, + withCapture( + logger, + { + feature: "marketing-pages", + layer: "event-handler", + name: "marketing-pages.onAuthUserSignedUpHandler", + }, + onAuthUserSignedUpHandler(queue), + ), + ); + if (marketingPagesContainer.isBound(MARKETING_PAGES_SYMBOLS.IOnAuthUserSignedUpHandler)) { + marketingPagesContainer.unbind(MARKETING_PAGES_SYMBOLS.IOnAuthUserSignedUpHandler); + } + marketingPagesContainer.bind(MARKETING_PAGES_SYMBOLS.IOnAuthUserSignedUpHandler).toConstantValue(wrappedAuthUserSignedUp); + bus?.subscribe(userSignedUpEvent, "marketing-pages", wrappedAuthUserSignedUp); } - marketingPagesContainer.bind(MARKETING_PAGES_SYMBOLS.IOnAuthUserSignedUpHandler).toConstantValue(wrappedAuthUserSignedUp); - bus?.subscribe(userSignedUpEvent, "marketing-pages", wrappedAuthUserSignedUp); // const wrappedSendWelcomeEmail = withSpan( tracer, diff --git a/packages/marketing-pages/src/di/bind-production.ts b/packages/marketing-pages/src/di/bind-production.ts index ef0af91..4447753 100644 --- a/packages/marketing-pages/src/di/bind-production.ts +++ b/packages/marketing-pages/src/di/bind-production.ts @@ -6,7 +6,6 @@ import { type ILogger, } from "@repo/core-shared/instrumentation"; import type { BindProductionContext } from "@repo/core-shared/di"; -import type { IJobQueue } from "@repo/core-shared/jobs"; import { marketingPagesContainer } from "./container"; import { MARKETING_PAGES_SYMBOLS } from "./symbols"; import { PagesRepository } from "../infrastructure/repositories/pages.repository"; @@ -130,24 +129,27 @@ export function bindProductionMarketingPages(ctx: BindProductionContext): void { // // onAuthUserSignedUpHandler subscription — generated, edit the handler file (not this block) for behavior. - const wrappedAuthUserSignedUp = withSpan( - tracer, - { name: "marketing-pages.onAuthUserSignedUpHandler", op: "event-handler" }, - withCapture( - logger, - { - feature: "marketing-pages", - layer: "event-handler", - name: "marketing-pages.onAuthUserSignedUpHandler", - }, - onAuthUserSignedUpHandler(queue as IJobQueue), - ), - ); - if (marketingPagesContainer.isBound(MARKETING_PAGES_SYMBOLS.IOnAuthUserSignedUpHandler)) { - marketingPagesContainer.unbind(MARKETING_PAGES_SYMBOLS.IOnAuthUserSignedUpHandler); + // queue is optional: guard so the handler is only bound when core-jobs is wired (Phase 3+). + if (queue) { + const wrappedAuthUserSignedUp = withSpan( + tracer, + { name: "marketing-pages.onAuthUserSignedUpHandler", op: "event-handler" }, + withCapture( + logger, + { + feature: "marketing-pages", + layer: "event-handler", + name: "marketing-pages.onAuthUserSignedUpHandler", + }, + onAuthUserSignedUpHandler(queue), + ), + ); + if (marketingPagesContainer.isBound(MARKETING_PAGES_SYMBOLS.IOnAuthUserSignedUpHandler)) { + marketingPagesContainer.unbind(MARKETING_PAGES_SYMBOLS.IOnAuthUserSignedUpHandler); + } + marketingPagesContainer.bind(MARKETING_PAGES_SYMBOLS.IOnAuthUserSignedUpHandler).toConstantValue(wrappedAuthUserSignedUp); + bus?.subscribe(userSignedUpEvent, "marketing-pages", wrappedAuthUserSignedUp); } - marketingPagesContainer.bind(MARKETING_PAGES_SYMBOLS.IOnAuthUserSignedUpHandler).toConstantValue(wrappedAuthUserSignedUp); - bus?.subscribe(userSignedUpEvent, "marketing-pages", wrappedAuthUserSignedUp); // const wrappedSendWelcomeEmail = withSpan( tracer,