37 lines
1.8 KiB
TypeScript
37 lines
1.8 KiB
TypeScript
import type { IAuditLog } from "./audit-log.interface";
|
|
import { attachBrand } from "@repo/core-shared/conformance";
|
|
|
|
/**
|
|
* Phantom-type brand attached at wrap time by `withAudit`. The conformance
|
|
* system uses this as the type-level seam for mutating use cases that
|
|
* declare `audits: [...]` in their manifest — without `__audited`, the
|
|
* binding is not assignable to `ProductionUseCase<I, O, M>` when M demands
|
|
* it. At runtime the brand is a non-enumerable property attached by
|
|
* `attachBrand` from `@repo/core-shared/conformance`, so the boot-time
|
|
* assertion can verify the binding went through the audit-aware path.
|
|
*/
|
|
export type Audited<F> = F & { readonly __audited: true };
|
|
|
|
/**
|
|
* Use-case wrapper applied at DI bind time. The wrapper is a thin closure
|
|
* that forwards to `fn` unchanged and carries the `__audited` brand. The
|
|
* forward closure (instead of returning `fn` directly) keeps the brand on
|
|
* a fresh function so the caller's original `fn` is not mutated — important
|
|
* when the same factory output is used elsewhere unwrapped (dev-seed paths,
|
|
* tests).
|
|
*/
|
|
export function withAudit<Args extends unknown[], R>(
|
|
// TODO: wire automated recording from manifest declarations.
|
|
// `audits[]` declarations. For now, the wrapper exists to:
|
|
// (1) require callers to pass the auditLog at bind time (dep is available)
|
|
// (2) attach the `__audited` brand so the boot-time assertion can verify
|
|
// mutating use cases were bound through the audit-aware path.
|
|
auditLog: IAuditLog,
|
|
fn: (...args: Args) => Promise<R>,
|
|
): Audited<(...args: Args) => Promise<R>> {
|
|
void auditLog;
|
|
const wrapped: (...args: Args) => Promise<R> = (...args) => fn(...args);
|
|
attachBrand(wrapped, "__audited");
|
|
return wrapped as Audited<(...args: Args) => Promise<R>>;
|
|
}
|