Skip to content

Commit 29c7218

Browse files
committed
enhanced documentation and examples
1 parent 6c0ad2a commit 29c7218

10 files changed

+338
-80
lines changed

README.md

Lines changed: 103 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,52 @@
22

33
# Fig Rule Engine Documentation
44

5-
## Overview
5+
---
6+
7+
## For everyone (plain language)
8+
9+
**What is this?**
10+
The Fig Rule Engine helps you automate decisions. You give it **data** (e.g. “this customer spent $600” or “this customer is gold”), you define **rules** (e.g. “if spend > $500 and status is gold, apply a discount”), and the engine tells you which rules passed or failed and what to do next.
11+
12+
**Why use it?**
13+
So you can run promotions, loyalty tiers, fraud checks, or any “if this, then that” logic in one place—without hard-coding every case in your app. Business people can understand the rules; developers plug in the data and run the engine.
614

7-
The **Fig Rule Engine** is a flexible tool designed to automate decision-making in business processes. It allows organizations to define custom rules, conditions, and actions that are evaluated against data points called "facts." This engine is particularly useful for customer management, fraud detection, loyalty programs, and personalized marketing.
15+
**How does it work in one sentence?**
16+
You register **facts** (your data), **conditions** (the checks, like “amount greater than 500”), **rules** (which conditions must pass and what happens when they do or don’t), then you **run** the engine and get a result (and optionally call APIs or do calculations).
817

9-
The Fig Rule Engine is modular and written in TypeScript, making it adaptable for various business cases. Facts, conditions, and rules are registered via a central **Name** registry and then evaluated by the **RuleEngine**.
18+
The rest of this document explains the same ideas in more detail: first for **everyone**, then **technical details** for developers.
19+
20+
---
21+
22+
## Overview (technical)
23+
24+
The **Fig Rule Engine** is a flexible tool designed to automate decision-making in business processes. It allows organizations to define custom rules, conditions, and actions that are evaluated against data points called **facts**. This engine is particularly useful for customer management, fraud detection, loyalty programs, and personalized marketing.
25+
26+
The Fig Rule Engine is modular and written in TypeScript. Facts, conditions, and rules are registered in a central registry and then evaluated by the **RuleEngine**.
1027

1128
---
1229

1330
## Table of Contents
1431

15-
1. [Installation](#installation)
16-
2. [API at a glance](#api-at-a-glance)
17-
3. [Core Concepts](#core-concepts)
18-
4. [Class Details](#class-details)
32+
1. [For everyone (plain language)](#for-everyone-plain-language)
33+
2. [Overview (technical)](#overview-technical)
34+
3. [Installation](#installation)
35+
4. [API at a glance](#api-at-a-glance)
36+
5. [Core Concepts](#core-concepts)
37+
6. [Class Details](#class-details)
1938
- [Fact Class](#fact-class)
2039
- [Condition Class](#condition-class)
2140
- [Conditions Class](#conditions-class)
2241
- [Rule Class](#rule-class)
2342
- [Action Class](#action-class)
2443
- [RuleEngine Class](#ruleengine-class)
2544
- [Name Class](#name-class)
26-
5. [Allowed Operators](#allowed-operators)
27-
6. [RuleEngine requirements](#ruleengine-requirements)
28-
7. [Example Use Cases](#example-use-cases)
45+
7. [Allowed Operators](#allowed-operators)
46+
8. [RuleEngine requirements](#ruleengine-requirements)
47+
9. [Example Use Cases](#example-use-cases)
2948
- [Runnable examples](#runnable-examples)
30-
8. [Error handling](#error-handling)
31-
9. [Testing](#testing)
49+
10. [Error handling](#error-handling)
50+
11. [Testing](#testing)
3251

3352
---
3453

@@ -68,17 +87,31 @@ TypeScript users get types from the package `types` field (`dist/index.d.ts`). W
6887

6988
## Core Concepts
7089

71-
### Facts
72-
**Facts** are individual pieces of data that the Rule Engine uses to evaluate rules. A fact can be an object (e.g. `{ amount: 500 }`) or a function `(params, almanac) => value` for dynamic data. Facts are the building blocks; all conditions and rules depend on them.
90+
### Facts (your data)
91+
**Facts** are the data the engine uses to evaluate rules. Examples: “transaction amount is 600”, “customer status is gold”. A fact can be a fixed object (e.g. `{ amount: 500 }`) or a function that returns data when the engine runs. All conditions and rules depend on facts.
92+
93+
### Conditions (the checks)
94+
**Conditions** are the criteria that must be met for a rule to trigger. Each condition checks one thing: e.g. “transaction amount greater than 500”, “customer status equal to gold”. You can combine conditions with “all must pass” (AND) or “at least one must pass” (OR).
7395

74-
### Conditions
75-
**Conditions** define the criteria that must be met for a rule to activate. Each condition evaluates a fact against an operator and a target value, with an optional `path` (e.g. `amount` or `$.price`) and optional `params`.
96+
### Rules (what happens when)
97+
**Rules** link a set of conditions to outcomes: when the conditions pass, the rule **fires** and you can run success logic (e.g. apply discount); when they fail, you can run failure logic (e.g. show “not eligible”). Each rule needs a name, conditions, an event, a priority, and both success and failure handlers to be executed by the engine.
7698

77-
### Rules
78-
**Rules** tie a set of conditions to an event, a priority, and **onSuccess** / **onFailure** handlers. When the conditions are met, the rule fires. **Important:** A rule is only registered with the engine if it has **name**, **conditions**, **event**, **priority**, **onSuccess**, and **onFailure** all set.
99+
### Actions (calculations and API calls)
100+
**Actions** are operations the engine or your code can run: arithmetic (e.g. tax = price × rate), date differences (e.g. days between two dates), or HTTP requests (e.g. fetch data from an API or POST a result to a webhook).
79101

80-
### Actions
81-
**Actions** are operations on data: arithmetic, date differences, booleans, or HTTP requests. They are used when you need to compute values or call APIs based on rule outcomes.
102+
### Key terms used in examples and code
103+
104+
| Term | Meaning |
105+
|------|--------|
106+
| **path** | The field name inside the fact object that the condition checks. E.g. `path('amount')` means “use the fact’s `amount` field”. |
107+
| **priority** | A number (≥ 1) that controls run order when multiple rules could fire: **higher number = runs first**. |
108+
| **getCondition** | A getter on a Condition that returns the condition in the shape the engine needs. You pass it into `conditions.all([...])` or `conditions.any([...])` to plug that check into a group. |
109+
| **event params** | Extra data attached to a rule when it fires (e.g. `{ discount: 10 }`, `{ threshold: 100 }`). Your code can read these to know *what value* was used (e.g. “10% off”, “above 100”). |
110+
| **results.events** | List of rules that fired; each event has a **type** and **params**. |
111+
| **results.almanacSuccess** | Whether the engine run completed successfully. |
112+
| **results.failureEvents** | Rules that were evaluated but their conditions did **not** pass (so they did not fire). |
113+
| **Action params** | For `new Action(data, params)`: **action** = which method to run (e.g. `'multiply'`, `'request'`); **value** = second argument to that method (e.g. tax rate); **dataPath** = key in `data` for the first argument (e.g. `'price'` → 100). |
114+
| **request(name, overrideData)** | **name** = key to store the HTTP response under (e.g. `getData.requestData['post']`); **overrideData** = for GET sent as query params, for POST sent as request body. |
82115

83116
---
84117

@@ -138,7 +171,7 @@ console.log(condition.getCondition);
138171

139172
- **Constructor:** `new Rule(name: string)`
140173
- **conditions(conditionsName)**: Links the rule to a named Conditions set (must exist).
141-
- **event(type, params)**: Sets the event type and optional params when the rule fires.
174+
- **event(type, params)**: When the rule fires, an event is emitted with a **type** (e.g. `'discount'`) and optional **params** (extra data for whoever handles the event). Params are arbitrary key-value data—e.g. `{ discount: 10 }` (how much to discount), `{ threshold: 100 }` (the cutoff value that defined “high value”), or `{ level: 'premium' }`—so listeners know both *what* happened and *with what values*.
142175
- **priority(n)**: Sets priority (number **≥ 1**). Higher values run first.
143176
- **onSuccess(fn)**, **onFailure(fn)**: Handlers `(event, almanac) => …`. **Both must be set** for the rule to be registered with the RuleEngine.
144177
- **Rule.find(name)**: Returns the **Name** object for that rule (with `getType`, `conditions`, `event`, etc.), not raw data.
@@ -190,7 +223,7 @@ console.log(action.getData.requestData.myRequest); // response data
190223
### RuleEngine Class
191224

192225
- **Constructor:** `new RuleEngine()` — Discovers facts and rules via **Name.findByType()** and registers them with the underlying engine. Only rules that have **name**, **conditions**, **event**, **priority**, **onSuccess**, and **onFailure** are added.
193-
- **run()**: Returns a **Promise** (engine result: events, almanac, etc.).
226+
- **run()**: Returns a **Promise** with the engine result. Important fields: **events** (array of rules that fired; each has `type` and `params`), **almanacSuccess** (whether the run succeeded), **failureEvents** (rules that were evaluated but did not pass). Your code can use these to apply discounts, log outcomes, or POST to an API.
194227
- **artifacts** (property): `{ facts?: Array<…>, rules?: Array<…> }` — snapshot of registered facts and rules.
195228

196229
#### Example
@@ -241,19 +274,55 @@ For a rule to be registered and executed by **RuleEngine**:
241274

242275
### Runnable examples
243276

244-
After `npm run build`, you can run these scripts from the project root:
277+
After `npm run build`, run any script from the project root (e.g. `node examples/01-high-spending-discount.mjs`).
278+
279+
| Script | In plain language | Technical note |
280+
|--------|-------------------|-----------------|
281+
| `01-high-spending-discount.mjs` | **Promo rule:** Give a discount only when the purchase is over $500 **and** the customer is gold. | AND conditions, success/failure handlers. |
282+
| `02-action-arithmetic.mjs` | **Calculate tax:** Multiply price by tax rate (e.g. 100 × 0.2 = 20). | Action arithmetic and `run()`. |
283+
| `03-date-difference.mjs` | **Date math:** Compute days, months, or years between two dates. | `numberOfDays`, `numberOfMonths`, `numberOfYears`. |
284+
| `04-boolean-action.mjs` | **Yes/no flags:** Set a result to true or false for use in other logic. | Action `true()` / `false()`. |
285+
| `05-or-conditions.mjs` | **Tier access:** Grant access if the customer is gold **or** silver (either is enough). | OR conditions (`any`). |
286+
| `06-rule-failure.mjs` | **When the rule doesn’t apply:** Purchase is too small, so no discount; show a “not eligible” outcome. | Rule does not fire; `onFailure` runs. |
287+
| `07-api-request.mjs` | **Call an API:** Fetch one record from a public demo API (no login). | HTTP GET with Action. |
288+
| `08-api-as-fact.mjs` | **Use API data in rules:** Fetch data from an API, treat it as a fact, then run rules on it (e.g. “is this post valid?”). | API response → Fact → RuleEngine. |
289+
| `09-api-notify.mjs` | **Send results to an API:** Run rules, then POST the result (e.g. which rules fired) to a webhook or endpoint. | RuleEngine result → HTTP POST. |
290+
291+
**How to read the examples:** Each file in `examples/` has a short **business summary** at the top (what the example does in plain language), then **line-by-line comments** in the code so both developers and non-developers can follow step by step. Run any example with `node examples/XX-name.mjs` after `npm run build`. At the end of each example, a **Sample result** is printed so you can see the exact structure of the output.
292+
293+
**Sample result and API payload structure:** When you run the examples, you’ll see output like the following.
294+
295+
*Structure of `ruleEngine.run()` result (rule examples 01, 05, 06, 08, 09):*
296+
297+
```json
298+
{
299+
"events": [
300+
{ "type": "discount", "params": { "discount": 10 } }
301+
],
302+
"almanacSuccess": true,
303+
"failureEvents": []
304+
}
305+
```
245306

246-
| Script | Description |
247-
|--------|-------------|
248-
| `node examples/01-high-spending-discount.mjs` | AND conditions: discount when amount > 500 and status is gold. |
249-
| `node examples/02-action-arithmetic.mjs` | **Action** arithmetic and **run()**: multiply (e.g. tax = price × rate). |
250-
| `node examples/03-date-difference.mjs` | **Action** date helpers: **numberOfDays**, **numberOfMonths**, **numberOfYears**. |
251-
| `node examples/04-boolean-action.mjs` | **Action** booleans: **true()** and **false()**. |
252-
| `node examples/05-or-conditions.mjs` | OR conditions (**any**): rule fires when status is gold **or** silver. |
253-
| `node examples/06-rule-failure.mjs` | Rule that does not fire; **onFailure** runs when conditions are not met. |
254-
| `node examples/07-api-request.mjs` | **Action** HTTP request: **setUrl**, **setMethod**, **request(name, overrideData)** using a public API (JSONPlaceholder). |
255-
| `node examples/08-api-as-fact.mjs` | **API as input:** fetch from API, set response as a **Fact**, run rules against it. |
256-
| `node examples/09-api-notify.mjs` | **API as notify:** run rules, then **POST** the rule result to an API endpoint. |
307+
- **events** — Array of rules that fired; each has **type** and **params**.
308+
- **almanacSuccess** — Whether the engine run completed successfully.
309+
- **failureEvents** — Rules that were evaluated but their conditions did not pass (optional; may be absent on the raw result).
310+
- **almanac** — Optional runtime fact storage from the engine (when present).
311+
312+
*Structure of the API payload body when POSTing rule result (example 09 — notify):*
313+
314+
```json
315+
{
316+
"events": [
317+
{ "type": "highValue", "params": { "threshold": 100 } }
318+
],
319+
"almanacSuccess": true,
320+
"failureEvents": [],
321+
"almanac": {}
322+
}
323+
```
324+
325+
Your webhook or API can read **events** (what fired and with what params), **almanacSuccess**, **failureEvents**, and **almanac** to log, audit, or react to the rule run.
257326

258327
### 1. High-spending customer discount
259328

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,69 @@
11
/**
22
* Example 1: High-spending customer discount
3-
* Run from project root: node examples/01-high-spending-discount.mjs
4-
* (Run "npm run build" first.)
3+
*
4+
* BUSINESS: Apply a 10% discount only when BOTH are true:
5+
* - Transaction amount is greater than $500
6+
* - Customer status is "gold"
7+
*
8+
* Run from project root (after "npm run build"):
9+
* node examples/01-high-spending-discount.mjs
510
*/
11+
612
import { Fact, Condition, Conditions, Rule, RuleEngine } from '../dist/index.js';
713

14+
// --- FACTS (the data the engine will check) ---
15+
16+
// Create a fact named "transaction" and set its data: one field "amount" = 600.
817
const transactionFact = new Fact('transaction');
918
transactionFact.setFact = { amount: 600 };
19+
20+
// Create a fact named "customer_status" and set its data: one field "status" = "gold".
1021
const customerStatusFact = new Fact('customer_status');
1122
customerStatusFact.setFact = { status: 'gold' };
1223

24+
// --- CONDITIONS (the checks we want to run) ---
25+
26+
// Condition 1: check the fact "transaction" — its "amount" field must be greater than 500.
27+
// path('amount') = the field name inside the fact object to check.
1328
const condition1 = new Condition();
1429
condition1.fact('transaction').operator('greaterThan').value(500).path('amount');
30+
31+
// Condition 2: check the fact "customer_status" — its "status" field must equal "gold".
1532
const condition2 = new Condition();
1633
condition2.fact('customer_status').operator('equal').value('gold').path('status');
1734

35+
// Group conditions: ALL must pass (AND logic). We name this group "promoEligibility".
36+
// getCondition = the condition in the shape the engine needs; we pass it into all().
1837
const conditions = new Conditions('promoEligibility');
1938
conditions.all([condition1.getCondition, condition2.getCondition]);
2039

40+
// --- RULE (what happens when conditions pass or fail) ---
41+
42+
// Create a rule named "applyDiscount" that uses the "promoEligibility" conditions.
2143
const rule = new Rule('applyDiscount');
22-
rule.conditions('promoEligibility')
44+
rule
45+
.conditions('promoEligibility')
46+
// discount: 10 = e.g. 10% or 10 currency units — your app decides; params are for whoever handles the event.
2347
.event('discount', { discount: 10 })
24-
.priority(1)
25-
.onSuccess(() => console.log('Discount applied successfully'))
26-
.onFailure(() => console.log('Discount could not be applied'));
48+
.priority(1) // Run order: higher number = runs first (when you have multiple rules).
49+
.onSuccess(() => console.log('Discount applied successfully')) // Called when conditions pass.
50+
.onFailure(() => console.log('Discount could not be applied')); // Called when conditions fail.
51+
52+
// --- RUN THE ENGINE ---
2753

54+
// The engine discovers all registered facts and rules, then evaluates them.
2855
const ruleEngine = new RuleEngine();
2956
const results = await ruleEngine.run();
57+
58+
// Results tell us which rules fired (events) and other details.
3059
console.log('Events:', results.events.length);
3160
console.log('First event type:', results.events[0]?.type);
32-
console.log('OK: High-spending discount example passed.\n');
61+
62+
console.log('\n--- Sample result (structure from ruleEngine.run()) ---');
63+
console.log(JSON.stringify({
64+
events: results.events,
65+
almanacSuccess: results.almanacSuccess,
66+
failureEvents: results.failureEvents ?? []
67+
}, null, 2));
68+
69+
console.log('\nOK: High-spending discount example passed.\n');

examples/02-action-arithmetic.mjs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
11
/**
2-
* Example 2: Action arithmetic and run()
2+
* Example 2: Action arithmetic (e.g. calculate tax)
3+
*
4+
* BUSINESS: Compute tax as "price × tax rate" (e.g. 100 × 0.2 = 20).
5+
* This shows how to use the Action class for calculations that your rules might need.
6+
*
37
* Run: node examples/02-action-arithmetic.mjs
48
*/
9+
510
import { Action } from '../dist/index.js';
611

12+
// Data we'll use: price and tax rate. The Action reads from this object.
713
const data = { price: 100, taxRate: 0.2 };
14+
15+
// Params: action = which method to run ('multiply'); value = second argument (0.2 = tax rate);
16+
// dataPath = key in data for the first argument ('price' → 100). So run() will compute 100 * 0.2.
817
const params = { action: 'multiply', value: 0.2, dataPath: 'price' };
918
const action = new Action(data, params);
1019

11-
// Compute tax: price * taxRate via run() (uses params.action, params.value, params.dataPath)
20+
// run() executes the action (multiply) and returns the result.
1221
const result = await action.run();
22+
1323
console.log('Tax (100 * 0.2):', result);
14-
console.log(result === 20 ? 'OK: Action arithmetic example passed.\n' : 'FAIL');
24+
25+
console.log('\n--- Sample result (structure from action.run()) ---');
26+
console.log(JSON.stringify({ result }, null, 2));
27+
28+
console.log(result === 20 ? '\nOK: Action arithmetic example passed.\n' : 'FAIL');

0 commit comments

Comments
 (0)