verdict-rules
Version:
A small, zero-dependency, async-native rule-evaluation engine. Compose conditions into one explainable pass/fail verdict, with sequential evaluation and real short-circuiting as a guarantee.
78 lines (77 loc) • 2.68 kB
TypeScript
import type { RuleResult } from "./result.js";
/**
* The contract every rule satisfies.
*
* A plain `interface` — structural typing: any object of the right shape
* is a `Rule`. No `implements` clause, no base class, no registration.
*
* `Rule<TContext>` is generic over the context it reads from, with no
* default type parameter. Dict-context is `Rule<Context>`, written out
* every time.
*
* ```ts
* const overEighteen: Rule<Context> = {
* name: "over_18",
* async evaluate(ctx) {
* return { ruleName: "over_18", passed: (ctx.age as number) >= 18 };
* },
* };
* ```
*/
export interface Rule<TContext> {
/**
* Unique identifier for this rule, used for engine lookups and to attribute
* a result back to its source.
*/
readonly name: string;
/** Optional group label. Rules sharing one can be run together. */
readonly group?: string | undefined;
/** Evaluate this rule against `context`. */
evaluate(context: TContext): Promise<RuleResult>;
}
/** Signature of the predicate {@link FunctionRule} wraps. */
export type RulePredicate<TContext> = (context: TContext) => Promise<RuleResult>;
/**
* Wraps a plain async predicate as a {@link Rule}.
*
* `TContext` is inferred from the wrapped predicate's own parameter type.
*/
export declare class FunctionRule<TContext> implements Rule<TContext> {
#private;
readonly name: string;
readonly group: string | undefined;
constructor(name: string, predicate: RulePredicate<TContext>, group?: string);
/** Runs the wrapped predicate and returns whatever it returns, unchanged. */
evaluate(context: TContext): Promise<RuleResult>;
}
/**
* Composite that passes only if every sub-rule passes.
*
* Short-circuits on the first failing sub-rule.
*
* An empty list passes vacuously.
*
* Every sub-rule must share the exact same `TContext`.
*/
export declare class AndRule<TContext> implements Rule<TContext> {
#private;
readonly name: string;
readonly group: string | undefined;
constructor(name: string, rules: readonly Rule<TContext>[], group?: string);
evaluate(context: TContext): Promise<RuleResult>;
}
/**
* Composite that passes as soon as any sub-rule passes.
*
* Short-circuits on the first passing sub-rule.
*
* An empty list fails vacuously. The same same-`TContext` requirement
* across sub-rules applies here too.
*/
export declare class OrRule<TContext> implements Rule<TContext> {
#private;
readonly name: string;
readonly group: string | undefined;
constructor(name: string, rules: readonly Rule<TContext>[], group?: string);
evaluate(context: TContext): Promise<RuleResult>;
}