# blackbox-rules

[![Version](https://img.shields.io/bitbucket/pipelines/ellipsistechnology/blackbox-rules-js.svg)](https://bitbucket.org/ellipsistechnology/blackbox-rules-js/addon/pipelines/home#!/)
[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg)](https://github.com/RichardLitt/standard-readme)

> Blackbox rulebase and basic rules engine.

The blackbox-rules package provides a basic framework for a distributed rules engine suitable for use in a Blackbox API. The project includes the rules engine and classes and interfaces for defining rules.

A Rule consist of a pre-condition, trigger, action and post-condition, all of which are Conditions. Conditions are made up of a type and left and right Values. Rules execute according to the following process:

1. Make pre-condition true,
2. Check if trigger Condition is true,
3. If trigger Condition is true make action Condition true,
4. Make post-condition true.

This package contains the basic components required to build a rulebase, but does not provide an implementation for numerous details such as the rule store (database) or mechanisms for interacting with remote rules. For default implementations of these required components, you can use the [blackbox-rules-utils]() package. For use of the rulebase within the Blackbox IOC container see the [blackbox-rules-services]() package.

For further information about the Blackbox Specification refer to the Blackbox website.

## Table of Contents

- [Install](#install)
- [Usage](#usage)
- [API](#api)
- [Maintainers](#maintainers)
- [Contributing](#contributing)
- [License](#license)

## Install

```
npm i blackbox-rules
```

## Usage

Create a rulebase:
```
import RuleBase from 'blackbox-rules'

const rulebase = new RuleBase({
  runPeriod: 100,
  store: new MyStore(),
  remoteValueProtocol: new MyRemoteValueProtocol(),
  variableStore: new MyVariableStore()
});
```

Create a rule with triggers and actions:
```
rulebase.when(trigger).then(action).withName('my new rule')
```

Create an equals condition from two values:
```
leftValue.eq(rightValue).withName('trigger')
```

Create a constant value:
```
rulebase.constantValue('xml').withName('type')
```

Execute rules:
```
rulebase.processRules()
```

Alternaively you can assign interrupts to your rules and then start the rulebase:
```
rule.interrupt = new MyInterrupt()
rulebase.addRule(rule)
rulebase.start()
```
If `start()` is called and a rule is encountered with no interrupt it will be given a default of `TimerInterrupt` with a timeout set to `rulebase.runPeriod`. To stop the rule execution simply call `rulebase.stop()`.

An example of blackbox-rules can be found on the Blackbox website.

## API

### RuleBase

`import RuleBase from 'blackbox-rules'`

```
constructor(params:{
  store?:Store,
  remoteValueProtocol?:RemoteValueProtocol,
  variableStore?:VariableStore,
  runPeriod?:number}
)
```

#### RuleBase CRUD

**`addValue(v:Value<any>)`**
**`getValue(valueName:string):Value<any>`**
**`removeValue(name:string)`**
**`replaceValue(v:Value<any>)`**
**`allValues():Value<any>[]`**

**`addCondition(c:Condition)`**
**`getCondition(conditionName:string):Condition`**
**`removeCondition(name:string)`**
**`replaceCondition(c:Condition)`**
**`allConditions():Condition[]`**

**`addRule(r:Rule)`**
**`getRule(ruleName:string):Rule`**
**`removeRule(name:string)`**
**`replaceRule(r:Rule)`**
**`allRules():Rule[]`**

#### Rule Processing

**`processRule(rule:Rule)`** Asynchronousely process a single rule.

**`processRules():Promise<boolean>`** Asynchronousely process all rules. Rules will only be processed if the time since the last call to `processRules()` is greater than `RuleBase.runPeriod`. This method of processing rules allows for manual handling of rule execution.

**`start()`** Start all rule interrupts. This method of processing rules allows for automatic handling of rule execution.

**`stop()`** Stop all rule interrupts.

#### RuleBase Factory Methods

**`when(trigger:ConditionData)`** Create a rule with a `when().then()` pattern.

**`unconditional():Condition`** Provides a Condition that always returns true.

**`constantValue<T>(v:T):ConstantValue<T>&WithName<ConstantValue<T>>`** Provides a Value whose get() method always returns v and cannot be changed; `set(v)` does nothing.

**`remoteValue<T>(remoteAddress:string, remoteValueName:string):RemoteValue<T>&WithName<RemoteValue<T>>`** Provides a value that is a proxy for a value in another rulebase.

**`dateTimeValue():DateTimeValue&WithName<DateTimeValue>`** Provides a read-only Value that gives the current date and time in a Date object.

**`logValue():LogValue&WithName<LogValue>`** Provides a write-only Value that logs the value given when calling `set(v)`.


### Store
An interface encapsulating the functionality required for storing Rules, Conditions, and Values.

### VariableStore
An interface encapsulating the fuctionality required for storing `VariableValue`s.

### RemoteValueProtocol
An interface encapsulating the funcitonality required to interact with a remote Value.

### RuleInterrupt
`start(rulebase:RuleBase, rule:Rule)` Called by the rulebase for each rule when `RuleBase.start()` is called. The Interrupt should set itself up ready to call `rulebase.processRule(rule)` when it's interrupt condition is met. For example, the `TimerInterrupt` setups up an interval timer to periodically process the rule.

`stop(rulebase:RuleBase, rule:Rule)` Called by the rulebase for each rule when `RuleBase.stop()` is called. The Interrupt should stop processing interrupts.

### TimerInterrupt
A `RuleInterrupt` that setups up an interval timer to periodically process the rule.

### Rule
The Rule class consists of a name and Conditions and can be constructed with the Rule constructor:
```
constructor(params:{
  name?:string
  preCondition?:Condition
  postCondition?:Condition
  trigger?:Condition
  action?:Condition
})
```
or with the RuleBase factory methods: `rulebase.when(trigger).then(action)`.

### Condition
The `Condition` interface includes a name and type, and `checkTrue():boolean` and `makeTrue()` methods. `checkTrue()` should return true if the condition is true, and `makeTrue()` changes the condition such that the next call to `checkTrue()` should return true. `checkTrue()` applies to triggers, while `makeTrue()` applies to pre-conditions, actions and post-conditions.

The `Condition` interface extends `Value<boolean>` since `Condition`s make up the left and right `Value`s of an `AndCondition` and an `OrCondition`.

The `ConditionImpl` class provides a default implementation of a `Condition`. It can be constructed with the `ConditionImpl` constructor:
```
constructor(params:{
  name?:string
  left?:Value<any>
  right?:Value<any>
  type?:string
  template?:any
})
```
however the `checkTrue()` and `makeTrue()` methods will throw exceptions and must be overwritten in subclasses or instances. `ConditionImpl` also provides factory methods to create `AndCondition`s and `OrCondition`s via the `and(cond:Condition)` and `or(cond:Condition)` methods.

Some basic `Condition`s are provided through the following classes:
`EqualsCondition`
`GreaterThanCondition`
`LessThanCondition`
`AndCondition`
`OrCondition`.

### Value
The `Value<T>` interface incudes a name and type, and `get():T` and `set(v:T)` methods.

The `ValueImpl<T>` class provides a default implementation of a `Value<T>`. It can be constructed with the `ValueImpl` constructor:
```
constructor(params:{
  name?:string
	type?:string
})
```
however the `get()` and `set()` methods will throw exceptions and must be overwritten in subclasses or instances. `ValueImpl` also provides factory methods to create `EqualsCondition`s, `LessThanCondition`s and `GreaterThanCondition`s via the `eq(rhs:Value<T>)`, `eq(rhs:Value<T>)` and `eq(rhs:Value<T>)` methods.

Some basic `Value`s are provided through the following classese:
`ConstantValue`
`DateTimeValue`
`LogValue`
`RemoteValue`
`VariableValue`

## Maintainers

[@ellipsistechnology](https://github.com/ellipsistechnology)

## Contributing

PRs accepted.

Small note: If editing the README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification.

## License

MIT © 2019 Ben Millar
