type-rules-engine
TypeScript icon, indicating that this package has built-in type declarations

0.1.0 • Public • Published

type-rules-engine

Simple, declarative and immutable rules engine for TypeScript.

build status codecov npm version

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:

Property Default Description
skipOnFirstNonTriggeredRule?: boolean false Stops execution when condition() fails, if set to true.
skipOnFirstAppliedRule?: boolean false Stops execution when action() runs successfully, if set to true.
skipOnFirstFailedRule?: boolean false Stops execution when action() throws error, if set to true.
throwOnError?: boolean false Throws when error occurred in condition() or action(), if set to true.
By default, it does not throw error inside rule execution except RulesEngineError.
debug?: boolean false Print 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

Package Sidebar

Install

npm i type-rules-engine

Weekly Downloads

1

Version

0.1.0

License

MIT

Unpacked Size

101 kB

Total Files

34

Last publish

Collaborators

  • seoco