feat(core-shared/conformance): defineFeature helper + manifest types

This commit is contained in:
2026-05-12 21:34:19 +02:00
parent a1fbd16d83
commit b3784ce255
2 changed files with 83 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
import { describe, it, expectTypeOf } from "vitest";
import { defineFeature, type FeatureManifest } from "@/conformance/define-feature";
describe("defineFeature", () => {
it("preserves literal types of a manifest declared with `as const`", () => {
const manifest = defineFeature({
name: "auth",
requiredCores: ["audit"],
useCases: {
signIn: {
mutates: false,
audits: [],
publishes: [],
consumes: [],
},
signUp: {
mutates: true,
audits: ["user.created"],
publishes: ["auth.signed-up"],
consumes: [],
},
},
realtimeChannels: [],
jobs: [],
} as const);
// Literal preservation: name is the literal "auth", not string
expectTypeOf(manifest.name).toEqualTypeOf<"auth">();
// Use-case keys preserved
expectTypeOf(manifest.useCases.signUp.audits).toEqualTypeOf<readonly ["user.created"]>();
expectTypeOf(manifest.useCases.signUp.mutates).toEqualTypeOf<true>();
expectTypeOf(manifest.useCases.signIn.mutates).toEqualTypeOf<false>();
});
it("FeatureManifest type accepts the shape", () => {
const manifest = defineFeature({
name: "blog",
requiredCores: [],
useCases: {},
realtimeChannels: [],
jobs: [],
} as const);
expectTypeOf(manifest).toMatchTypeOf<FeatureManifest>();
});
});

View File

@@ -0,0 +1,38 @@
/**
* Per-use-case manifest entry. Declares what the use case does at the contract
* level: whether it mutates state, what audit events it emits, what cross-feature
* events it publishes or consumes. The conformance system reads these to
* derive binding-slot types and to verify code against manifest declarations.
*/
export type UseCaseManifest = {
readonly mutates: boolean;
readonly audits: readonly string[];
readonly publishes: readonly string[];
readonly consumes: readonly string[];
};
/**
* The feature-level manifest. One per feature package, conventionally exported
* as `<featureName>Manifest` from `src/feature.manifest.ts`.
*/
export type FeatureManifest = {
readonly name: string;
readonly requiredCores: readonly string[];
readonly useCases: { readonly [k: string]: UseCaseManifest };
readonly realtimeChannels: readonly string[];
readonly jobs: readonly string[];
};
/**
* Identity helper that exists purely to widen the input type to satisfy
* `FeatureManifest` while preserving the literal types of the `as const`
* input. Downstream types (`ProductionUseCase<I, O, M>`) consume the
* preserved literals to derive binding-slot brand requirements.
*
* Usage:
*
* export const authManifest = defineFeature({ name: "auth", ... } as const);
*/
export function defineFeature<const M extends FeatureManifest>(manifest: M): M {
return manifest;
}