Skip to content

Commit

Permalink
feat(core): Support sub rules
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewbrg committed Oct 1, 2024
1 parent d49bbf7 commit 37f4151
Show file tree
Hide file tree
Showing 18 changed files with 474 additions and 172 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ each condition's evaluation.
- Lightweight with **zero dependencies** & Fast _(10,000 rule evaluations in ~35-40ms)_
- Fluent rule builder tool (ORM style)
- Both Simple & Granular rule evaluation
- Infinite condition nesting
- Sub-rules & infinitely nested conditions
- Dynamic criteria mutation
- Supports Criteria objects with nested properties
- Rule validation & debugging tools
Expand Down Expand Up @@ -889,9 +889,9 @@ yarn build
Tests are written in Jest and can be run with the following commands:

```bash
npm run jest --testPathPattern=test --detectOpenHandles --color --forceExit
npm run jest --testPathPattern=test --color --forceExit --silent
```

```bash
yarn jest --testPathPattern=test --detectOpenHandles --color --forceExit
yarn jest --testPathPattern=test --color --forceExit --silent
```
60 changes: 60 additions & 0 deletions src/builder/base-builder.abstract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
Rule,
SubRule,
Operator,
Condition,
Constraint,
ConditionType,
} from "../types/rule";

export abstract class BaseBuilder {
/** Stores the rule being constructed */
#rule: Rule = { conditions: [] };

/**
* Adds a node (in the root) to the rule being constructed
* @param node The node to add to the rule
*/
add(node: Condition): BaseBuilder {
(this.#rule.conditions as Condition[]).push(node);
return this;
}

/**
* Creates a new condition node
* @param type The type of condition
* @param nodes Any child nodes of the condition
* @param result The result of the condition node (for granular rules)
* @param subRule A sub-rule to apply to the condition
*/
condition(
type: ConditionType,
nodes: Condition[ConditionType],
result?: Condition["result"],
subRule?: SubRule
): Condition {
return {
[type]: nodes,
...(subRule ? { rule: subRule } : {}),
...(result ? { result } : {}),
};
}

/**
* Creates a new constraint node
* @param field The field to apply the constraint to
* @param operator The operator to apply to the field
* @param value The value to compare the field to
*/
constraint(
field: string,
operator: Operator,
value: Constraint["value"]
): Constraint {
return {
field,
operator,
value,
};
}
}
48 changes: 48 additions & 0 deletions src/builder/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Rule } from "../types/rule";
import { Validator } from "../services";
import { RuleError } from "../types/error";
import { SubRuleBuilder } from "./sub-rule-builder";
import { BaseBuilder } from "./base-builder.abstract";

export class Builder extends BaseBuilder {
constructor(validator: Validator) {
super();
this.#validator = validator;
}

/** Stores the rule being constructed */
#rule: Rule = { conditions: [] };

/** Holds a reference to the Validator class */
#validator: Validator;

/**
* Sets the default value of the rule being constructed
* @param value The default value of the rule
*/
default(value: Rule["default"]): Builder {
this.#rule.default = value;
return this;
}

/**
* Builds the rule being and returns it
* @param validate Whether to validate the rule before returning it
* @throws Error if validation is enabled and the rule is invalid
*/
build(validate?: boolean): Rule {
if (!validate) return this.#rule;

const validationResult = this.#validator.validate(this.#rule);
if (validationResult.isValid) return this.#rule;

throw new RuleError(validationResult);
}

/**
* Creates a new sub-rule builder
*/
subRule(): SubRuleBuilder {
return new SubRuleBuilder();
}
}
3 changes: 3 additions & 0 deletions src/builder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./base-builder.abstract";
export * from "./builder";
export * from "./sub-rule-builder";
16 changes: 16 additions & 0 deletions src/builder/sub-rule-builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { SubRule } from "../types/rule";
import { BaseBuilder } from "./base-builder.abstract";

export class SubRuleBuilder extends BaseBuilder {
/** Stores the rule being constructed */
#rule: SubRule = { conditions: [], result: undefined };

/**
* Sets the result of the subrule being constructed
* @param value The default value of the rule
*/
result(value: SubRule["result"]): SubRuleBuilder {
this.#rule.result = value;
return this;
}
}
92 changes: 0 additions & 92 deletions src/services/builder.ts

This file was deleted.

Loading

0 comments on commit 37f4151

Please sign in to comment.