feat(core-shared): add rate-limit type primitives and manifest field
Adds IRateLimit, RateLimitBudget, RateLimitDecision to a new rate-limit sub-module; adds RateLimited<F> brand and isRateLimited predicate following the Captured/ConsentChecked pattern; extends UseCaseManifest with rateLimit?: readonly RateLimitBudget[] so features can declare rate-limit gates in their manifests. Exports new types from @repo/core-shared/rate-limit and @repo/core-shared/conformance. Blocks stories 02, 03, and 04. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"generatedAt": "2026-05-19T21:51:40.371Z",
|
||||
"commit": "5151454",
|
||||
"generatedAt": "2026-05-20T08:21:18.294Z",
|
||||
"commit": "a633561",
|
||||
"repo": {
|
||||
"statements": 97.38,
|
||||
"branches": 92.19,
|
||||
"functions": 97.1,
|
||||
"branches": 92.2,
|
||||
"functions": 97.11,
|
||||
"lines": 97.38,
|
||||
"counts": {
|
||||
"lf": 5801,
|
||||
"lh": 5649,
|
||||
"brf": 1165,
|
||||
"brh": 1074,
|
||||
"fnf": 345,
|
||||
"fnh": 335
|
||||
"lf": 5807,
|
||||
"lh": 5655,
|
||||
"brf": 1166,
|
||||
"brh": 1075,
|
||||
"fnf": 346,
|
||||
"fnh": 336
|
||||
}
|
||||
},
|
||||
"byPackage": {
|
||||
@@ -101,17 +101,17 @@
|
||||
}
|
||||
},
|
||||
"@repo/core-shared": {
|
||||
"statements": 98.03,
|
||||
"branches": 95.89,
|
||||
"functions": 92.08,
|
||||
"lines": 98.03,
|
||||
"statements": 98.04,
|
||||
"branches": 95.9,
|
||||
"functions": 92.16,
|
||||
"lines": 98.04,
|
||||
"counts": {
|
||||
"lf": 1067,
|
||||
"lh": 1046,
|
||||
"brf": 316,
|
||||
"brh": 303,
|
||||
"fnf": 101,
|
||||
"fnh": 93
|
||||
"lf": 1073,
|
||||
"lh": 1052,
|
||||
"brf": 317,
|
||||
"brh": 304,
|
||||
"fnf": 102,
|
||||
"fnh": 94
|
||||
}
|
||||
},
|
||||
"@repo/core-ui": {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./audit": "./src/audit/index.ts",
|
||||
"./rate-limit": "./src/rate-limit/index.ts",
|
||||
"./conformance": "./src/conformance/index.ts",
|
||||
"./conformance/coverage": "./src/conformance/coverage.ts",
|
||||
"./di": "./src/di/index.ts",
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
isCaptured,
|
||||
isAudited,
|
||||
isAnalyzed,
|
||||
isRateLimited,
|
||||
} from "@/conformance/brand-runtime";
|
||||
|
||||
describe("brand-runtime", () => {
|
||||
@@ -23,6 +24,7 @@ describe("brand-runtime", () => {
|
||||
expect(isCaptured(fn)).toBe(false);
|
||||
expect(isAudited(fn)).toBe(false);
|
||||
expect(isAnalyzed(fn)).toBe(false);
|
||||
expect(isRateLimited(fn)).toBe(false);
|
||||
});
|
||||
|
||||
it("predicates discriminate between brands", () => {
|
||||
@@ -45,6 +47,11 @@ describe("brand-runtime", () => {
|
||||
const analyzed = () => {};
|
||||
attachBrand(analyzed, "__analyzed");
|
||||
expect(isAnalyzed(analyzed)).toBe(true);
|
||||
|
||||
const rateLimited = () => {};
|
||||
attachBrand(rateLimited, "__rateLimited");
|
||||
expect(isRateLimited(rateLimited)).toBe(true);
|
||||
expect(isInstrumented(rateLimited)).toBe(false);
|
||||
});
|
||||
|
||||
it("composing brands stacks them on the same function", () => {
|
||||
@@ -53,10 +60,12 @@ describe("brand-runtime", () => {
|
||||
attachBrand(fn, "__captured");
|
||||
attachBrand(fn, "__audited");
|
||||
attachBrand(fn, "__analyzed");
|
||||
attachBrand(fn, "__rateLimited");
|
||||
expect(isInstrumented(fn)).toBe(true);
|
||||
expect(isCaptured(fn)).toBe(true);
|
||||
expect(isAudited(fn)).toBe(true);
|
||||
expect(isAnalyzed(fn)).toBe(true);
|
||||
expect(isRateLimited(fn)).toBe(true);
|
||||
});
|
||||
|
||||
it("attached brands are non-writable and non-configurable", () => {
|
||||
|
||||
@@ -13,14 +13,15 @@
|
||||
* commitment, not a mutable flag.
|
||||
*/
|
||||
|
||||
import type { Analyzed, ConsentChecked } from "./brands";
|
||||
import type { Analyzed, ConsentChecked, RateLimited } from "./brands";
|
||||
|
||||
type Brand =
|
||||
| "__instrumented"
|
||||
| "__captured"
|
||||
| "__audited"
|
||||
| "__analyzed"
|
||||
| "__consentChecked";
|
||||
| "__consentChecked"
|
||||
| "__rateLimited";
|
||||
|
||||
/**
|
||||
* Attaches the brand as a non-enumerable property on the given function.
|
||||
@@ -68,3 +69,9 @@ export function isConsentChecked<F extends object>(
|
||||
): fn is ConsentChecked<F> {
|
||||
return hasBrand(fn, "__consentChecked");
|
||||
}
|
||||
|
||||
export function isRateLimited<F extends object>(
|
||||
fn: unknown,
|
||||
): fn is RateLimited<F> {
|
||||
return hasBrand(fn, "__rateLimited");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expectTypeOf } from "vitest";
|
||||
import type { Instrumented, Captured } from "@/conformance/brands";
|
||||
import type { Instrumented, Captured, RateLimited } from "@/conformance/brands";
|
||||
|
||||
describe("brand types", () => {
|
||||
it("Instrumented<F> is structurally F plus a phantom flag", () => {
|
||||
@@ -16,6 +16,12 @@ describe("brand types", () => {
|
||||
expectTypeOf<Captured<Fn>["__captured"]>().toEqualTypeOf<true>();
|
||||
});
|
||||
|
||||
it("RateLimited<F> is structurally F plus a phantom flag", () => {
|
||||
type Fn = (n: number) => Promise<string>;
|
||||
expectTypeOf<RateLimited<Fn>>().toBeCallableWith(1);
|
||||
expectTypeOf<RateLimited<Fn>["__rateLimited"]>().toEqualTypeOf<true>();
|
||||
});
|
||||
|
||||
it("brands compose without conflict", () => {
|
||||
type Fn = (n: number) => Promise<string>;
|
||||
type Both = Instrumented<Fn> & Captured<Fn>;
|
||||
|
||||
@@ -11,3 +11,4 @@ export type Instrumented<F> = F & { readonly __instrumented: true };
|
||||
export type Captured<F> = F & { readonly __captured: true };
|
||||
export type Analyzed<F> = F & { readonly __analyzed: true };
|
||||
export type ConsentChecked<F> = F & { readonly __consentChecked: true };
|
||||
export type RateLimited<F> = F & { readonly __rateLimited: true };
|
||||
|
||||
@@ -2,7 +2,9 @@ import { describe, it, expect, expectTypeOf } from "vitest";
|
||||
import {
|
||||
defineFeature,
|
||||
type FeatureManifest,
|
||||
type UseCaseManifest,
|
||||
} from "@/conformance/define-feature";
|
||||
import type { RateLimitBudget } from "@/rate-limit/rate-limit.interface";
|
||||
import {
|
||||
DEFAULT_COVERAGE_BANDS,
|
||||
DEFAULT_MUTATION_SCORE,
|
||||
@@ -58,6 +60,41 @@ describe("defineFeature", () => {
|
||||
expectTypeOf(manifest).toMatchTypeOf<FeatureManifest>();
|
||||
});
|
||||
|
||||
it("accepts an optional rateLimit array on a use case", () => {
|
||||
const manifest = defineFeature({
|
||||
name: "auth",
|
||||
requiredCores: [],
|
||||
useCases: {
|
||||
signIn: {
|
||||
mutates: false,
|
||||
audits: [],
|
||||
publishes: [],
|
||||
consumes: [],
|
||||
rateLimit: [{ name: "login", window: "1m", budget: 5 }],
|
||||
},
|
||||
},
|
||||
realtimeChannels: [],
|
||||
jobs: [],
|
||||
} as const);
|
||||
|
||||
expectTypeOf(manifest).toMatchTypeOf<FeatureManifest>();
|
||||
expectTypeOf(manifest.useCases.signIn.rateLimit).toMatchTypeOf<
|
||||
readonly RateLimitBudget[] | undefined
|
||||
>();
|
||||
});
|
||||
|
||||
it("use case without rateLimit satisfies UseCaseManifest", () => {
|
||||
const uc: UseCaseManifest = {
|
||||
mutates: false,
|
||||
audits: [],
|
||||
publishes: [],
|
||||
consumes: [],
|
||||
};
|
||||
expectTypeOf(uc.rateLimit).toEqualTypeOf<
|
||||
readonly RateLimitBudget[] | undefined
|
||||
>();
|
||||
});
|
||||
|
||||
it("accepts an optional coverage section", () => {
|
||||
const manifest = defineFeature({
|
||||
name: "auth",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { CoverageManifest } from "./coverage";
|
||||
import type { RateLimitBudget } from "../rate-limit/rate-limit.interface";
|
||||
|
||||
/**
|
||||
* Per-use-case manifest entry. Declares what the use case does at the contract
|
||||
@@ -12,6 +13,7 @@ export type UseCaseManifest = {
|
||||
readonly publishes: readonly string[];
|
||||
readonly consumes: readonly string[];
|
||||
readonly analyticsEvents?: readonly string[];
|
||||
readonly rateLimit?: readonly RateLimitBudget[];
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@ export type {
|
||||
Captured,
|
||||
Analyzed,
|
||||
ConsentChecked,
|
||||
RateLimited,
|
||||
} from "./brands";
|
||||
export type { FeatureManifest, UseCaseManifest } from "./define-feature";
|
||||
export { defineFeature } from "./define-feature";
|
||||
@@ -29,6 +30,7 @@ export {
|
||||
isAudited,
|
||||
isAnalyzed,
|
||||
isConsentChecked,
|
||||
isRateLimited,
|
||||
} from "./brand-runtime";
|
||||
export { ConformanceError } from "./conformance-error";
|
||||
export { assertFeatureConformance } from "./assert-bindings";
|
||||
|
||||
5
packages/core-shared/src/rate-limit/index.ts
Normal file
5
packages/core-shared/src/rate-limit/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export type {
|
||||
IRateLimit,
|
||||
RateLimitBudget,
|
||||
RateLimitDecision,
|
||||
} from "./rate-limit.interface";
|
||||
@@ -0,0 +1,41 @@
|
||||
import { describe, it, expectTypeOf } from "vitest";
|
||||
import type {
|
||||
IRateLimit,
|
||||
RateLimitBudget,
|
||||
RateLimitDecision,
|
||||
} from "@/rate-limit/rate-limit.interface";
|
||||
|
||||
describe("rate-limit types", () => {
|
||||
it("RateLimitBudget has the expected shape", () => {
|
||||
expectTypeOf<RateLimitBudget>().toMatchTypeOf<{
|
||||
name: string;
|
||||
window: string;
|
||||
budget: number;
|
||||
}>();
|
||||
});
|
||||
|
||||
it("RateLimitDecision has the expected shape", () => {
|
||||
expectTypeOf<RateLimitDecision>().toMatchTypeOf<{
|
||||
allowed: boolean;
|
||||
remaining: number;
|
||||
resetAt: Date;
|
||||
}>();
|
||||
});
|
||||
|
||||
it("IRateLimit.consume returns Promise<RateLimitDecision>", () => {
|
||||
expectTypeOf<
|
||||
IRateLimit["consume"]
|
||||
>().returns.resolves.toEqualTypeOf<RateLimitDecision>();
|
||||
});
|
||||
|
||||
it("IRateLimit.consume accepts optional weight", () => {
|
||||
type ConsumeParams = Parameters<IRateLimit["consume"]>;
|
||||
expectTypeOf<ConsumeParams[0]>().toEqualTypeOf<string>();
|
||||
expectTypeOf<ConsumeParams[1]>().toEqualTypeOf<string>();
|
||||
expectTypeOf<ConsumeParams[2]>().toEqualTypeOf<number | undefined>();
|
||||
});
|
||||
|
||||
it("IRateLimit.reset returns Promise<void>", () => {
|
||||
expectTypeOf<IRateLimit["reset"]>().returns.resolves.toEqualTypeOf<void>();
|
||||
});
|
||||
});
|
||||
20
packages/core-shared/src/rate-limit/rate-limit.interface.ts
Normal file
20
packages/core-shared/src/rate-limit/rate-limit.interface.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export type RateLimitBudget = {
|
||||
name: string;
|
||||
window: string;
|
||||
budget: number;
|
||||
};
|
||||
|
||||
export type RateLimitDecision = {
|
||||
allowed: boolean;
|
||||
remaining: number;
|
||||
resetAt: Date;
|
||||
};
|
||||
|
||||
export interface IRateLimit {
|
||||
consume(
|
||||
budgetName: string,
|
||||
key: string,
|
||||
weight?: number,
|
||||
): Promise<RateLimitDecision>;
|
||||
reset(budgetName: string, key: string): Promise<void>;
|
||||
}
|
||||
Reference in New Issue
Block a user