@infitx/decision
v1.3.6
Published
Decision tables engine
Readme
Decision Library
A lightweight rule engine for evaluating facts against configurable rules and generating decisions. The library uses pattern matching to determine which rules apply to a given fact and returns corresponding decisions.
Features
- Pattern Matching: Uses
@infitx/matchfor flexible fact matching - Priority-based Evaluation: Rules can have explicit or implicit priorities
- Multiple Decision Support: Can return first match or all matching rules
- YAML Configuration: Define rules in human-readable YAML format
- Type Safety: Built-in support for various data types including dates and timestamps
Installation
const decision = require('./');Basic Usage
const decision = require('./');
const { decide, rules } = decision('./config.yaml');
const fact = { type: 'transfer', amount: 500 };
const result = decide(fact);
console.log(result);Examples
Example 1: Single Decision with Implicit Priority
Rules are evaluated in the order they appear in the configuration. When rules are defined as an object, the key becomes the rule name and serves as the implicit priority (alphabetically sorted).
- YAML Configuration
rules: transfer-approval: when: { type: transfer, amount: { max: 1000 } } then: { expect: { approved: true, reason: approved } } transfer-rejection: when: { type: transfer, amount: { min: 1001 } } then: { expect: { approved: false, reason: limit } }Decision Table
| Rule | Condition | Decision | |--------------------- |------------------------------------- |----------------------------------------------- | |transfer-approval | type=transfer AND amount ≤ 1000 | expect: approved=true, reason=approved | |transfer-rejection | type=transfer AND amount ≥ 1001 | expect: approved=false, reason=limit |
Code Example
const decision = require('./'); const { decide } = decision('./transfer-config.yaml'); // Test case 1: Approved transfer const fact1 = { type: 'transfer', amount: 500 }; console.log(decide(fact1)); // Output: [{ // rule: 'transfer-approval', // decision: 'expect', // approved: true, // reason: 'approved' // }] // Test case 2: Rejected transfer const fact2 = { type: 'transfer', amount: 1500 }; console.log(decide(fact2)); // Output: [{ // rule: 'transfer-rejection', // decision: 'expect', // approved: false, // reason: 'limit' // }]
Example 2: Single Decision with Explicit Priority
When you need specific evaluation order, pass an array of rules with the required order.
YAML Configuration
rules: - rule: high-value-check when: { type: transaction, amount: { min: 10000 } } then: { require: { manualReview: true, approvers: 2 } } - rule: standard-check when: { type: transaction, amount: { max: 9999 } } then: { allow: { automated: true, approvers: 0 } }Decision Table
| Rule | Condition | Decision | |-------------------|--------------------------------------|-----------------------------------------------| | high-value-check | type=transaction AND amount ≥ 10000 | require: manualReview=true, approvers=2 | | standard-check | type=transaction AND amount ≤ 9999 | allow: automated=true, approvers=0 |
Code Example
const decision = require('./'); const { decide, rules } = decision('./priority-config.yaml'); console.log('Rules in evaluation order:', rules.map(r => ({ rule: r.rule }))); // Output: [ // { rule: 'high-value-check' }, // { rule: 'standard-check } // ] const fact1 = { type: 'transaction', amount: 15000 }; console.log(decide(fact1)); // Output: [{ // rule: 'high-value-check', // decision: 'require', // manualReview: true, // approvers: 2 // }] const fact2 = { type: 'transaction', amount: 500 }; console.log(decide(fact2)); // Output: [{ // rule: 'standard-check', // decision: 'allow', // automated: true, // approvers: 0 // }]
Example 3: Multiple Decisions
A single fact can trigger multiple decisions from one rule. This is useful for scenarios where you need to take multiple actions based on a single condition.
YAML Configuration
rules: withdrawal-high-amount: when: { type: withdrawal, amount: { min: 501 } } then: expect: { approved: false, reason: large } notification: { type: alert, recipient: manager } audit: { level: warning, message: "Large withdrawal attempted" } withdrawal-normal: when: { type: withdrawal, amount: { max: 500 } } then: expect: { approved: true, reason: small } audit: { level: info, message: "Withdrawal approved" }Decision Table
| Rule | Condition | Decisions | |------------------------|----------------------------------|-------------------------------------------------------------------------------------------------------------------------------------| | withdrawal-high-amount | type=withdrawal AND amount ≥ 501 | 1. expect: approved=false, reason=large2. notification: type=alert, recipient=manager3. audit: level=warning, message=... | | withdrawal-normal | type=withdrawal AND amount ≤ 500 | 1. expect: approved=true, reason=small2. audit: level=info, message=... |
Code Example
const decision = require('./'); const { decide } = decision('./withdrawal-config.yaml'); // Large withdrawal - triggers multiple decisions const fact1 = { type: 'withdrawal', amount: 700 }; console.log(decide(fact1)); // Output: [ // { // rule: 'withdrawal-high-amount', // decision: 'expect', // approved: false, // reason: 'large' // }, { // rule: 'withdrawal-high-amount', // decision: 'notification', // type: 'alert', // recipient: 'manager' // }, { // rule: 'withdrawal-high-amount', // decision: 'audit', // level: 'warning', // message: 'Large withdrawal attempted' // } // ] // Normal withdrawal const fact2 = { type: 'withdrawal', amount: 300 }; console.log(decide(fact2)); // Output: [ // { // rule: 'withdrawal-normal', // decision: 'expect', // approved: true, // reason: 'small' // }, { // rule: 'withdrawal-normal', // decision: 'audit', // level: 'info', // message: 'Withdrawal approved' // } // ]
Example 4: All Matching Rules
By default, decide() returns only the first matching rule. Use the all
parameter to get all matching rules.
YAML Configuration
rules: fraud-check: when: { type: transaction, amount: { min: 5000 } } then: { verify: { fraudCheck: true } } compliance-check: when: { type: transaction, amount: { min: 3000 } } then: { verify: { amlCheck: true } } standard-process: when: { type: transaction } then: { process: { standard: true } }Decision Table
| Rule | Condition | Decision | |-------------------|--------------------------------------|-------------------------------------| | fraud-check | type=transaction AND amount ≥ 5000 | verify: fraudCheck=true | | compliance-check | type=transaction AND amount ≥ 3000 | verify: amlCheck=true | | standard-process | type=transaction | process: standard=true |
Code Example
const decision = require('./'); const { decide } = decision('./all-rules-config.yaml'); const fact = { type: 'transaction', amount: 6000 }; // Get only first matching rule (default) console.log(decide(fact)); // Output: [{ rule: 'fraud-check', decision: 'verify', fraudCheck: true }] // Get all matching rules console.log(decide(fact, true)); // Output: [ // { rule: 'fraud-check', decision: 'verify', fraudCheck: true }, // { rule: 'compliance-check', decision: 'verify', amlCheck: true }, // { rule: 'standard-process', decision: 'process', standard: true } // ]
YAML Configuration Format
Basic Structure
rules:
rule-name:
when: <condition>
then: <decision>
priority: <number or string> # Optional, defaults to rule name if omittedOr as an array:
rules:
- rule: rule-name
when: <condition>
then: <decision>Components
rules
Can be either:
Object: Keys are rule names, values are rule definitions. Rules are sorted alphabetically by name (implicit priority) or by
priorityfield if present.Array: Rules definitions with explicit order. When an array is used, the processing order is the order of items in the array.
when(Condition)
A pattern matching object that defines when a rule applies. Supports:
Exact match:
{ type: transfer }Range match:
{ amount: { min: 100, max: 1000 } }Minimum:
{ amount: { min: 1000 } }Maximum:
{ amount: { max: 1000 } }Complex patterns: Nested objects, arrays, and any pattern supported by
@infitx/matchthen(Decision)
Defines the outcomes when a rule matches. Structure:
then:
<decision-type>: <decision-value>
<another-decision>: <another-value>Each decision becomes an object in the result array:
{ rule: 'rule-name', decision: 'decision-type', ...decision-value }priority(Optional)Numeric: Lower numbers evaluated first
String: Lexicographic ordering
Implicit: If omitted, rule name is used as priority
Complete Example
# Object format with implicit priority
rules:
high-priority-rule:
when: { urgent: true }
then: { action: { escalate: true } }
low-priority-rule:
when: { urgent: false }
then: { action: { queue: true } }
---
# Array format with explicit priority
rules:
- rule: critical
when: { severity: critical }
then:
alert: { immediate: true }
notify: { team: on call }
- rule: warning
when: { severity: warning }
then:
alert: { delayed: true }
- rule: info
when: { severity: info }
then:
log: { level: info }API Reference
decision(config)
Creates a decision engine instance.
Parameters:
config(String | Object): Path to YAML file or configuration object
Returns: Object with:
rules: Array of rules in evaluation orderdecide(fact, all): Function to evaluate factstests: Test cases from configuration (if present)
decide(fact, all = false)
Evaluates a fact against configured rules.
Parameters:
fact(Object): The fact to evaluateall(Boolean): If true, returns all matching rules; if false, returns only first match
Returns: Array of decision objects:
[
{ rule: 'rule-name', decision: 'decision-type', ...decision-value }
]Returns null (or empty array if all=true) if no rules match.
Testing
The library supports inline test definitions in YAML:
rules:
# ... rule definitions
tests:
- fact: { type: transfer, amount: 500 }
expected:
- approved: true
reason: approved
rule: transfer-approval
decision: expectRun tests with Jest:
const decision = require('./');
const { decide, tests } = decision('./config.yaml');
tests.forEach(({ fact, expected }) => {
test(`fact: ${JSON.stringify(fact)}`, () => {
expect(decide(fact)).toEqual(expected);
});
});