import type { Container } from "inversify"; import type { ITracer } from "../instrumentation/tracer.interface"; import type { ILogger } from "../instrumentation/logger.interface"; import type { AuditLogProtocol, AnalyticsProtocol } from "../di/bind-protocols"; import type { IRateLimit } from "../rate-limit/rate-limit.interface"; import { withSpan } from "../instrumentation/with-span"; import { withCapture } from "../instrumentation/with-capture"; import { withRateLimit } from "../rate-limit/with-rate-limit"; import { attachBrand } from "./brand-runtime"; export type WireUseCaseOptions< Deps extends unknown[], FnArgs extends unknown[], R, > = { container: Container; symbol: symbol; factory: (...deps: Deps) => (...args: FnArgs) => Promise; deps: Deps; feature: string; layer: string; name: string; tracer: ITracer; logger: ILogger; analytics?: AnalyticsProtocol; auditLog?: AuditLogProtocol; rateLimit?: IRateLimit; }; /** * Encapsulates the withSpan(withCapture(withAudit?(factory(deps)))) composition * and performs the container bind step. Callers pass options and get back a * brand-stacked wired value that is also bound to the container symbol. * * Idempotent: if the symbol is already bound, the old binding is replaced. * * withAudit lives in @repo/core-audit which core-shared cannot import (dependency * inversion: core-audit depends on core-shared, not vice versa). The audit branch * here replicates the same semantics — forwarding wrapper + __audited brand — * without introducing the circular dependency. */ export function wireUseCase< Deps extends unknown[], FnArgs extends unknown[], R, >(opts: WireUseCaseOptions): (...args: FnArgs) => Promise { const { container, symbol, factory, deps, feature, layer, name, tracer, logger, analytics, auditLog, rateLimit, } = opts; const spanName = `${feature}.${name}`; const captureTags = { feature, layer, name: spanName }; const raw = factory(...deps); // rateLimit is innermost — wraps raw before analytics/audit. Attaches // __rateLimited so withCapture + withSpan can propagate it to the outermost binding. let toWrap: (...args: FnArgs) => Promise = rateLimit !== undefined ? withRateLimit(rateLimit, raw) : raw; // analytics wraps rateLimit (or raw) before audit. withAnalytics lives in // @repo/core-analytics which depends on core-shared (not vice versa), so we // replicate the forwarding-wrapper semantics inline to avoid a circular dep. if (analytics !== undefined) { void analytics; // reserved for future automated event recording from manifest declarations const prev = toWrap; const analyzed: (...args: FnArgs) => Promise = (...args) => prev(...args); attachBrand(analyzed, "__analyzed"); // propagate __rateLimited from inner so withCapture/withSpan see it if ( (prev as unknown as Record)["__rateLimited"] === true ) { attachBrand(analyzed, "__rateLimited"); } toWrap = analyzed; } if (auditLog !== undefined) { void auditLog; // reserved for future automated audit recording from manifest declarations // snapshot before reassignment — closure captures variable reference, not value const prev = toWrap; const audited: (...args: FnArgs) => Promise = (...args) => prev(...args); attachBrand(audited, "__audited"); // propagate __analyzed and __rateLimited from inner so withCapture/withSpan // can see them on the outermost binding if ((prev as unknown as Record)["__analyzed"] === true) { attachBrand(audited, "__analyzed"); } if ( (prev as unknown as Record)["__rateLimited"] === true ) { attachBrand(audited, "__rateLimited"); } toWrap = audited; } const wired = withSpan( tracer, { name: spanName, op: layer }, withCapture(logger, captureTags, toWrap), ); if (container.isBound(symbol)) { container.unbind(symbol); } container.bind(symbol).toConstantValue(wired); return wired; }