Uecko_ERP/packages/rdx-ddd/src/specification.ts
2025-09-14 12:50:29 +02:00

133 lines
3.7 KiB
TypeScript

export interface IBaseSpecification<T> {
isSatisfiedBy(candidate: T): Promise<boolean>;
}
export interface ICompositeSpecification<T> extends IBaseSpecification<T> {
and(other: ICompositeSpecification<T>): ICompositeSpecification<T>;
andNot(other: ICompositeSpecification<T>): ICompositeSpecification<T>;
or(other: ICompositeSpecification<T>): ICompositeSpecification<T>;
orNot(other: ICompositeSpecification<T>): ICompositeSpecification<T>;
not(): ICompositeSpecification<T>;
}
export abstract class CompositeSpecification<T> implements ICompositeSpecification<T> {
abstract isSatisfiedBy(candidate: T): Promise<boolean>;
public and(other: ICompositeSpecification<T>): ICompositeSpecification<T> {
return new AndSpecification<T>(this, other);
}
public andNot(other: ICompositeSpecification<T>): ICompositeSpecification<T> {
return new AndNotSpecification<T>(this, other);
}
public or(other: ICompositeSpecification<T>): ICompositeSpecification<T> {
return new OrSpecification<T>(this, other);
}
public orNot(other: ICompositeSpecification<T>): ICompositeSpecification<T> {
return new OrNotSpecification<T>(this, other);
}
public not(): ICompositeSpecification<T> {
return new NotSpecification<T>(this);
}
}
class AndSpecification<T> extends CompositeSpecification<T> {
constructor(
private readonly left: ICompositeSpecification<T>,
private readonly right: ICompositeSpecification<T>
) {
super();
}
public async isSatisfiedBy(candidate: T): Promise<boolean> {
const leftResult = await this.left.isSatisfiedBy(candidate);
if (!leftResult) return false;
const rightResult = await this.right.isSatisfiedBy(candidate);
return rightResult;
}
toString(): string {
return `(${this.left.toString()} and ${this.right.toString()})`;
}
}
class AndNotSpecification<T> extends AndSpecification<T> {
public async isSatisfiedBy(candidate: T): Promise<boolean> {
return (await super.isSatisfiedBy(candidate)) !== true;
}
toString(): string {
return `not ${super.toString()}`;
}
}
class OrSpecification<T> extends CompositeSpecification<T> {
constructor(
private readonly left: ICompositeSpecification<T>,
private readonly right: ICompositeSpecification<T>
) {
super();
}
public async isSatisfiedBy(candidate: T): Promise<boolean> {
const leftResult = await this.left.isSatisfiedBy(candidate);
if (leftResult) return true;
const rightResult = await this.right.isSatisfiedBy(candidate);
return rightResult;
}
toString(): string {
return `(${this.left.toString()} or ${this.right.toString()})`;
}
}
class OrNotSpecification<T> extends OrSpecification<T> {
public async isSatisfiedBy(candidate: T): Promise<boolean> {
return (await super.isSatisfiedBy(candidate)) !== true;
}
toString(): string {
return `not ${super.toString()}`;
}
}
export class NotSpecification<T> extends CompositeSpecification<T> {
constructor(private readonly spec: ICompositeSpecification<T>) {
super();
}
public async isSatisfiedBy(candidate: T): Promise<boolean> {
return !this.spec.isSatisfiedBy(candidate);
}
toString(): string {
return `(not ${this.spec.toString()})`;
}
}
export class RangeSpecification<T> extends CompositeSpecification<T> {
private readonly min: T;
private readonly max: T;
private readonly compareFn: (a: T, b: T) => number;
constructor(min: T, max: T, compareFn: (a: T, b: T) => number) {
super();
this.min = min;
this.max = max;
this.compareFn = compareFn;
}
public async isSatisfiedBy(candidate: T): Promise<boolean> {
return this.compareFn(candidate, this.min) >= 0 && this.compareFn(candidate, this.max) <= 0;
}
toString(): string {
return `range [${this.min}, ${this.max}]`;
}
}