feat(core-shared/conformance): defineFeature helper + manifest types
This commit is contained in:
45
packages/core-shared/src/conformance/define-feature.test.ts
Normal file
45
packages/core-shared/src/conformance/define-feature.test.ts
Normal 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>();
|
||||||
|
});
|
||||||
|
});
|
||||||
38
packages/core-shared/src/conformance/define-feature.ts
Normal file
38
packages/core-shared/src/conformance/define-feature.ts
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user