feat(core-shared/conformance): ProductionUseCase<I, O, M> branded slot type
This commit is contained in:
@@ -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;
|
||||
});
|
||||
});
|
||||
24
packages/core-shared/src/conformance/production-use-case.ts
Normal file
24
packages/core-shared/src/conformance/production-use-case.ts
Normal 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);
|
||||
Reference in New Issue
Block a user