0.1.0 • Published 3 years ago

type-rules-engine v0.1.0

Weekly downloads
-
License
MIT
Repository
github
Last release
3 years ago

Highlights

  • Easy to use API.
  • Supports various ways to define rules.
  • Provides immutable fact object, powered by immer.
  • Fully tested.

Installation

reflect-metadata package is required, since type-rules-engine provides decorator based rule definition.

npm install type-rules-engine reflect-metadata
# or
yarn add type-rules-engine reflect-metadata

And be sure to reflect-metadata is imported before using type-rules-engine

import "reflect-metadata";

Last, a little tsconfig.json tweak is needed.

{
    "compilerOptions": {
        ...
        "experimentalDecorators": true,
        ...
    }
}

Usage

// 1. Define fact object
const feedCatFact = {
  says: 'meow',
  isHungry: true,
  weight: 5.5,
  feedingCount: 0,
};

// 2. Define rule - using builder API
const feedCatRule = new RuleBuilder<typeof feedCatFact>()
  .name('feed-cat-rule')
  .description('Hungry cats meowing for food')
  .priority(1)
  .when(({ getFact }) => getFact().says === 'meow' && getFact().isHungry)
  .then(({ setFact }) => {
    setFact(fact => {
      fact.feedingCount++;
      fact.isHungry = false;
    });
  })
  .then(({ setFact }) => {
    setFact(fact => {
      fact.says = 'purr';
      fact.weight += 0.1;
    });
  })
  .build();

// or using class and decorators
@Rule({ 
  name: 'feed-cat-rule', 
  description: 'Hungry cats meowing for food',
  priority: 1,
})
class FeedCatRule extends RuleDraft<typeof feedCatFact> {
  @When()
  isCatHungry() {
    const { says, isHungry } = this.getFact();
    return says === 'meow' && isHungry;
  }
  
  @Then({ order: 1 })
  feedCat() {
    this.setFact(fact => {
      fact.feedingCount++;
      fact.isHungry = false;
    });
  }

  @Then({ order: 2 })
  gainWeight() {
    this.setFact(fact => {
      fact.says = 'purr';
      fact.weight += 0.1;
    });
  }
}
const feedCarRule = new FeedCatRule()

// 3. Fire rules engine
const result = await new RulesEngine().fact(feedCatFact).rules([feedCatRule]).fire();
expect(result.triggeredRules).toEqual(['feed-cat-rule']);
expect(result.fact).toEqual({
  says: 'purr',
  isHungry: false,
  weight: 5.6,
  feedingCount: 1,
});

more usages can be found here.

API Reference

We've already covered most APIs in above part, and I believe that it's self-explanatory enough. I'll mention here only about advanced configs and edge cases.

RulesEngine

RulesEngine provides useful options like stop evaluating rules at some point or log result for debugging purpose.

RulesEngineConfig:

PropertyDefaultDescription
skipOnFirstNonTriggeredRule?: booleanfalseStops execution when condition() fails, if set to true.
skipOnFirstAppliedRule?: booleanfalseStops execution when action() runs successfully, if set to true.
skipOnFirstFailedRule?: booleanfalseStops execution when action() throws error, if set to true.
throwOnError?: booleanfalseThrows when error occurred in condition() or action(), if set to true. By default, it does not throw error inside rule execution except RulesEngineError.
debug?: booleanfalsePrint evaluation result using console.log, if set to true.

Immutability

We're in JavaScript world, and we all love immutability. Unlike most rules engines, type-rules-engine keeps fact objects immutable, powered by immer.

const fact = { status: 'initialized' };
const rule = new RuleBuilder<typeof fact>()
  .when(() => true)
  .then(({ getFact, setFact }) => {
    // ☠️ Updating fact directly is not allowed.
    getFact().status = 'updated'
    
    // 👌 Instead, use `setFact`.
    setFact(fact => {
      fact.status = 'updated';
    });
  })
  .build();

const result = await new RulesEngine().fact(fact).rules([rule]).fire();

// Updates inside rules engine never affect original fact object.
expect(fact.status).toEqual('initialized');
expect(result.fact.status).toEqual('updated');

Note that setFact is just a wrapper around immer's produce method. See this page for more descriptions.

Road map

Features that are planned:

  • Custom logger support. Replace default console.log on debug mode.
  • Rule composition. Something like this
  • Support for expression language. (Still looking for nice language implementation)

Inspirations

easy-rules which is written in Java inspired a lot of concepts and core API designs of this library.

Contributing

  1. Fork this repository
  2. Create new feature branch
  3. Write your codes and commit
  4. Make all tests and linter rules pass
  5. Push and make pull request