feat(core-shared): add wireUseCase helper to conformance barrel
Encapsulates withSpan(withCapture(withAudit?(factory(deps)))) composition
and container binding into a single helper, eliminating the structural
boilerplate clone groups repeated across every feature binder pair.
Callers pass { container, symbol, factory, deps, feature, layer, name,
tracer, logger, auditLog? } and get back a fully brand-stacked, container-
bound wired value. Idempotent: unbinds an existing symbol before rebinding.
withAudit lives in core-audit which core-shared cannot import (dependency
inversion: core-audit depends on core-shared). The audit path here replicates
the same semantics — forwarding wrapper + __audited brand — without the
circular dependency.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,3 +25,5 @@ export {
|
||||
} from "./brand-runtime";
|
||||
export { ConformanceError } from "./conformance-error";
|
||||
export { assertFeatureConformance } from "./assert-bindings";
|
||||
export { wireUseCase } from "./wire-use-case";
|
||||
export type { WireUseCaseOptions } from "./wire-use-case";
|
||||
|
||||
277
packages/core-shared/src/conformance/wire-use-case.test.ts
Normal file
277
packages/core-shared/src/conformance/wire-use-case.test.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import "reflect-metadata";
|
||||
import { Container } from "inversify";
|
||||
import { wireUseCase } from "@/conformance/wire-use-case";
|
||||
import {
|
||||
isInstrumented,
|
||||
isCaptured,
|
||||
isAudited,
|
||||
} from "@/conformance/brand-runtime";
|
||||
import type {
|
||||
ITracer,
|
||||
ISpan,
|
||||
SpanOpts,
|
||||
} from "@/instrumentation/tracer.interface";
|
||||
import type { ILogger } from "@/instrumentation/logger.interface";
|
||||
import type { AuditLogProtocol } from "@/di/bind-protocols";
|
||||
|
||||
function makeTracer() {
|
||||
const calls: SpanOpts[] = [];
|
||||
const tracer: ITracer = {
|
||||
startSpan: vi.fn(async (opts, fn) => {
|
||||
calls.push(opts);
|
||||
const span: ISpan = { setAttribute: () => {}, setStatus: () => {} };
|
||||
return fn(span);
|
||||
}),
|
||||
};
|
||||
return { tracer, calls };
|
||||
}
|
||||
|
||||
function makeLogger(): ILogger & {
|
||||
captureException: ReturnType<typeof vi.fn>;
|
||||
} {
|
||||
return {
|
||||
captureException: vi.fn(),
|
||||
captureMessage: vi.fn(),
|
||||
addBreadcrumb: vi.fn(),
|
||||
setUser: vi.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
function makeAuditLog(): AuditLogProtocol {
|
||||
return { record: vi.fn() };
|
||||
}
|
||||
|
||||
const doubleFactory = () => async (x: number) => x * 2;
|
||||
|
||||
describe("wireUseCase — no-audit path", () => {
|
||||
it("attaches Instrumented + Captured brands, no Audited brand", () => {
|
||||
const { tracer } = makeTracer();
|
||||
const logger = makeLogger();
|
||||
const container = new Container();
|
||||
const sym = Symbol("test.double");
|
||||
|
||||
const wired = wireUseCase({
|
||||
container,
|
||||
symbol: sym,
|
||||
factory: doubleFactory,
|
||||
deps: [],
|
||||
feature: "test",
|
||||
layer: "use-case",
|
||||
name: "double",
|
||||
tracer,
|
||||
logger,
|
||||
});
|
||||
|
||||
expect(isInstrumented(wired)).toBe(true);
|
||||
expect(isCaptured(wired)).toBe(true);
|
||||
expect(isAudited(wired)).toBe(false);
|
||||
});
|
||||
|
||||
it("executes the factory result on invocation", async () => {
|
||||
const { tracer } = makeTracer();
|
||||
const logger = makeLogger();
|
||||
const container = new Container();
|
||||
const sym = Symbol("test.double");
|
||||
|
||||
const wired = wireUseCase({
|
||||
container,
|
||||
symbol: sym,
|
||||
factory: doubleFactory,
|
||||
deps: [],
|
||||
feature: "test",
|
||||
layer: "use-case",
|
||||
name: "double",
|
||||
tracer,
|
||||
logger,
|
||||
});
|
||||
|
||||
await expect(wired(3)).resolves.toBe(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe("wireUseCase — audit path", () => {
|
||||
it("attaches Instrumented + Captured + Audited brands when auditLog is provided", () => {
|
||||
const { tracer } = makeTracer();
|
||||
const logger = makeLogger();
|
||||
const container = new Container();
|
||||
const sym = Symbol("test.double");
|
||||
const auditLog = makeAuditLog();
|
||||
|
||||
const wired = wireUseCase({
|
||||
container,
|
||||
symbol: sym,
|
||||
factory: doubleFactory,
|
||||
deps: [],
|
||||
feature: "test",
|
||||
layer: "use-case",
|
||||
name: "double",
|
||||
tracer,
|
||||
logger,
|
||||
auditLog,
|
||||
});
|
||||
|
||||
expect(isInstrumented(wired)).toBe(true);
|
||||
expect(isCaptured(wired)).toBe(true);
|
||||
expect(isAudited(wired)).toBe(true);
|
||||
});
|
||||
|
||||
it("executes the factory result on invocation (audit path)", async () => {
|
||||
const { tracer } = makeTracer();
|
||||
const logger = makeLogger();
|
||||
const container = new Container();
|
||||
const sym = Symbol("test.double");
|
||||
const auditLog = makeAuditLog();
|
||||
|
||||
const wired = wireUseCase({
|
||||
container,
|
||||
symbol: sym,
|
||||
factory: doubleFactory,
|
||||
deps: [],
|
||||
feature: "test",
|
||||
layer: "use-case",
|
||||
name: "double",
|
||||
tracer,
|
||||
logger,
|
||||
auditLog,
|
||||
});
|
||||
|
||||
await expect(wired(5)).resolves.toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe("wireUseCase — span-name derivation", () => {
|
||||
it("derives span name as <feature>.<name> and uses layer as op", async () => {
|
||||
const { tracer, calls } = makeTracer();
|
||||
const logger = makeLogger();
|
||||
const container = new Container();
|
||||
const sym = Symbol("auth.signIn");
|
||||
|
||||
const wired = wireUseCase({
|
||||
container,
|
||||
symbol: sym,
|
||||
factory: doubleFactory,
|
||||
deps: [],
|
||||
feature: "auth",
|
||||
layer: "use-case",
|
||||
name: "signIn",
|
||||
tracer,
|
||||
logger,
|
||||
});
|
||||
|
||||
await wired(1);
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0]).toMatchObject({ name: "auth.signIn", op: "use-case" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("wireUseCase — capture-tag structure", () => {
|
||||
it("captures with { feature, layer, name: '<feature>.<name>' } when an error is thrown", async () => {
|
||||
const { tracer } = makeTracer();
|
||||
const logger = makeLogger();
|
||||
const container = new Container();
|
||||
const sym = Symbol("blog.getArticles");
|
||||
const err = new Error("boom");
|
||||
|
||||
const wired = wireUseCase({
|
||||
container,
|
||||
symbol: sym,
|
||||
factory: () => async () => {
|
||||
throw err;
|
||||
},
|
||||
deps: [],
|
||||
feature: "blog",
|
||||
layer: "use-case",
|
||||
name: "getArticles",
|
||||
tracer,
|
||||
logger,
|
||||
});
|
||||
|
||||
await expect(wired()).rejects.toBe(err);
|
||||
expect(logger.captureException).toHaveBeenCalledWith(err, {
|
||||
tags: { feature: "blog", layer: "use-case", name: "blog.getArticles" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("wireUseCase — container binding", () => {
|
||||
it("binds the wired value to the container symbol", () => {
|
||||
const { tracer } = makeTracer();
|
||||
const logger = makeLogger();
|
||||
const container = new Container();
|
||||
const sym = Symbol("test.double");
|
||||
|
||||
const wired = wireUseCase({
|
||||
container,
|
||||
symbol: sym,
|
||||
factory: doubleFactory,
|
||||
deps: [],
|
||||
feature: "test",
|
||||
layer: "use-case",
|
||||
name: "double",
|
||||
tracer,
|
||||
logger,
|
||||
});
|
||||
|
||||
expect(container.get(sym)).toBe(wired);
|
||||
});
|
||||
|
||||
it("passes deps tuple to the factory", async () => {
|
||||
const { tracer } = makeTracer();
|
||||
const logger = makeLogger();
|
||||
const container = new Container();
|
||||
const sym = Symbol("test.scale");
|
||||
|
||||
const scaleFactory = (multiplier: number) => async (x: number) =>
|
||||
x * multiplier;
|
||||
|
||||
const wired = wireUseCase({
|
||||
container,
|
||||
symbol: sym,
|
||||
factory: scaleFactory,
|
||||
deps: [3] as [number],
|
||||
feature: "test",
|
||||
layer: "use-case",
|
||||
name: "scale",
|
||||
tracer,
|
||||
logger,
|
||||
});
|
||||
|
||||
await expect(wired(4)).resolves.toBe(12);
|
||||
});
|
||||
});
|
||||
|
||||
describe("wireUseCase — idempotent re-bind", () => {
|
||||
it("unbinds the existing binding and replaces it when the symbol is already bound", () => {
|
||||
const { tracer } = makeTracer();
|
||||
const logger = makeLogger();
|
||||
const container = new Container();
|
||||
const sym = Symbol("test.double");
|
||||
|
||||
wireUseCase({
|
||||
container,
|
||||
symbol: sym,
|
||||
factory: doubleFactory,
|
||||
deps: [],
|
||||
feature: "test",
|
||||
layer: "use-case",
|
||||
name: "double",
|
||||
tracer,
|
||||
logger,
|
||||
});
|
||||
|
||||
const second = wireUseCase({
|
||||
container,
|
||||
symbol: sym,
|
||||
factory: doubleFactory,
|
||||
deps: [],
|
||||
feature: "test",
|
||||
layer: "use-case",
|
||||
name: "double",
|
||||
tracer,
|
||||
logger,
|
||||
});
|
||||
|
||||
expect(container.get(sym)).toBe(second);
|
||||
});
|
||||
});
|
||||
83
packages/core-shared/src/conformance/wire-use-case.ts
Normal file
83
packages/core-shared/src/conformance/wire-use-case.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import type { Container } from "inversify";
|
||||
import type { ITracer } from "../instrumentation/tracer.interface";
|
||||
import type { ILogger } from "../instrumentation/logger.interface";
|
||||
import type { AuditLogProtocol } from "../di/bind-protocols";
|
||||
import { withSpan } from "../instrumentation/with-span";
|
||||
import { withCapture } from "../instrumentation/with-capture";
|
||||
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<R>;
|
||||
deps: Deps;
|
||||
feature: string;
|
||||
layer: string;
|
||||
name: string;
|
||||
tracer: ITracer;
|
||||
logger: ILogger;
|
||||
auditLog?: AuditLogProtocol;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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<Deps, FnArgs, R>): (...args: FnArgs) => Promise<R> {
|
||||
const {
|
||||
container,
|
||||
symbol,
|
||||
factory,
|
||||
deps,
|
||||
feature,
|
||||
layer,
|
||||
name,
|
||||
tracer,
|
||||
logger,
|
||||
auditLog,
|
||||
} = opts;
|
||||
|
||||
const spanName = `${feature}.${name}`;
|
||||
const captureTags = { feature, layer, name: spanName };
|
||||
|
||||
const raw = factory(...deps);
|
||||
|
||||
let toWrap: (...args: FnArgs) => Promise<R>;
|
||||
if (auditLog !== undefined) {
|
||||
void auditLog; // reserved for future automated audit recording from manifest declarations
|
||||
const audited: (...args: FnArgs) => Promise<R> = (...args) => raw(...args);
|
||||
attachBrand(audited, "__audited");
|
||||
toWrap = audited;
|
||||
} else {
|
||||
toWrap = raw;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user