feat(core-shared/conformance): ProductionUseCase<I, O, M> branded slot type

This commit is contained in:
2026-05-12 21:35:59 +02:00
parent b3784ce255
commit 82cfde9c93
2 changed files with 58 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
import { describe, it, expectTypeOf } from "vitest";
import type { ProductionUseCase } from "@/conformance/production-use-case";
import type { Instrumented, Captured } from "@/conformance/brands";
describe("ProductionUseCase<I, O, M>", () => {
it("requires Instrumented + Captured for any use case", () => {
type Manifest = {
mutates: false;
audits: readonly [];
publishes: readonly [];
consumes: readonly [];
};
type Slot = ProductionUseCase<{ x: number }, { y: string }, Manifest>;
type Wrapped = Instrumented<(input: { x: number }) => Promise<{ y: string }>> &
Captured<(input: { x: number }) => Promise<{ y: string }>>;
expectTypeOf<Wrapped>().toMatchTypeOf<Slot>();
});
it("a plain factory is NOT assignable to the slot", () => {
type Manifest = {
mutates: false;
audits: readonly [];
publishes: readonly [];
consumes: readonly [];
};
type Slot = ProductionUseCase<{ x: number }, { y: string }, Manifest>;
const factory = async (input: { x: number }) => ({ y: String(input.x) });
// @ts-expect-error — factory has no __instrumented / __captured brand
const bad: Slot = factory;
void bad;
});
});

View File

@@ -0,0 +1,24 @@
import type { UseCaseManifest } from "./define-feature";
import type { Instrumented, Captured } from "./brands";
/**
* Type-level binding slot for production use cases. Derived from the manifest
* entry: every binding must be Instrumented + Captured; mutating use cases
* that declare audits additionally must be Audited. The Audited brand lives
* in `@repo/core-audit` because the wrap helper that attaches it depends on
* `IAuditLog` — feature packages import the merged slot type implicitly
* by typing their bindings as `ProductionUseCase<I, O, AuthManifest["useCases"]["signIn"]>`.
*
* The Audited requirement is encoded conditionally so this type stays usable
* without depending on core-audit. When `mutates: true` AND `audits` is
* non-empty, the slot demands a marker type with a `__audited` flag; the
* concrete `Audited<F>` from core-audit satisfies it.
*/
export type ProductionUseCase<I, O, M extends UseCaseManifest> =
& Instrumented<(input: I) => Promise<O>>
& Captured<(input: I) => Promise<O>>
& (M["mutates"] extends true
? M["audits"]["length"] extends 0
? unknown
: { readonly __audited: true }
: unknown);