Skip to content

Commit 3c1975e

Browse files
committed
Support passing a custom Almanac
Support for passing a custom almanac to the run options in the engine.
1 parent 25f9f7f commit 3c1975e

File tree

6 files changed

+142
-8
lines changed

6 files changed

+142
-8
lines changed

docs/engine.md

+10
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,16 @@ const {
269269
```
270270
Link to the [Almanac documentation](./almanac.md)
271271

272+
Optionally, you may specify a specific almanac instance via the almanac property.
273+
274+
```js
275+
// create a custom Almanac
276+
const myCustomAlmanac = new CustomAlmanac();
277+
278+
// run the engine with the custom almanac
279+
await engine.run({}, { almanac: myCustomAlmanac })
280+
```
281+
272282
### engine.stop() -> Engine
273283

274284
Stops the rules engine from running the next priority set of Rules. All remaining rules will be resolved as undefined,

examples/12-using-custom-almanac.js

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
'use strict'
2+
3+
require('colors')
4+
const { Almanac, Engine } = require('json-rules-engine')
5+
6+
/**
7+
* Almanac that support piping values through named functions
8+
*/
9+
class PipedAlmanac extends Almanac {
10+
constructor (options) {
11+
super(options)
12+
this.pipes = new Map()
13+
}
14+
15+
addPipe (name, pipe) {
16+
this.pipes.set(name, pipe)
17+
}
18+
19+
factValue (factId, params, path) {
20+
let pipes = []
21+
if (params && 'pipes' in params && Array.isArray(params.pipes)) {
22+
pipes = params.pipes
23+
delete params.pipes
24+
}
25+
return super.factValue(factId, params, path).then(value => {
26+
return pipes.reduce((value, pipeName) => {
27+
const pipe = this.pipes.get(pipeName)
28+
if (pipe) {
29+
return pipe(value)
30+
}
31+
return value
32+
}, value)
33+
})
34+
}
35+
}
36+
37+
async function start () {
38+
const engine = new Engine()
39+
.addRule({
40+
conditions: {
41+
all: [
42+
{
43+
fact: 'age',
44+
params: {
45+
// the addOne pipe adds one to the value
46+
pipes: ['addOne']
47+
},
48+
operator: 'greaterThanInclusive',
49+
value: 21
50+
}
51+
]
52+
},
53+
event: {
54+
type: 'Over 21(ish)'
55+
}
56+
})
57+
58+
engine.on('success', async (event, almanac) => {
59+
const name = await almanac.factValue('name')
60+
const age = await almanac.factValue('age')
61+
console.log(`${name} is ${age} years old and ${'is'.green} ${event.type}`)
62+
})
63+
64+
engine.on('failure', async (event, almanac) => {
65+
const name = await almanac.factValue('name')
66+
const age = await almanac.factValue('age')
67+
console.log(`${name} is ${age} years old and ${'is not'.red} ${event.type}`)
68+
})
69+
70+
const createAlmanacWithPipes = () => {
71+
const almanac = new PipedAlmanac()
72+
almanac.addPipe('addOne', (v) => v + 1)
73+
return almanac
74+
}
75+
76+
// first run Bob who is less than 20
77+
await engine.run({ name: 'Bob', age: 19 }, { almanac: createAlmanacWithPipes() })
78+
79+
// second run Alice who is 21
80+
await engine.run({ name: 'Alice', age: 21 }, { almanac: createAlmanacWithPipes() })
81+
82+
// third run Chad who is 20
83+
await engine.run({ name: 'Chad', age: 20 }, { almanac: createAlmanacWithPipes() })
84+
}
85+
86+
start()
87+
88+
/*
89+
* OUTPUT:
90+
*
91+
* Bob is 19 years old and is not Over 21(ish)
92+
* Alice is 21 years old and is Over 21(ish)
93+
* Chad is 20 years old and is Over 21(ish)
94+
*/

src/engine.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -261,14 +261,15 @@ class Engine extends EventEmitter {
261261
* @param {Object} runOptions - run options
262262
* @return {Promise} resolves when the engine has completed running
263263
*/
264-
run (runtimeFacts = {}) {
264+
run (runtimeFacts = {}, runOptions = {}) {
265265
debug('engine::run started')
266266
this.status = RUNNING
267-
const almanacOptions = {
267+
268+
const almanac = runOptions.almanac || new Almanac({
268269
allowUndefinedFacts: this.allowUndefinedFacts,
269270
pathResolver: this.pathResolver
270-
}
271-
const almanac = new Almanac(almanacOptions)
271+
})
272+
272273
this.facts.forEach(fact => {
273274
almanac.addFact(fact)
274275
})

src/json-rules-engine.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import Engine from './engine'
22
import Fact from './fact'
33
import Rule from './rule'
44
import Operator from './operator'
5+
import Almanac from './almanac'
56

6-
export { Fact, Rule, Operator, Engine }
7+
export { Fact, Rule, Operator, Engine, Almanac }
78
export default function (rules, options) {
89
return new Engine(rules, options)
910
}

test/engine-run.test.js

+20
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,24 @@ describe('Engine: run', () => {
113113
})
114114
})
115115
})
116+
117+
describe('custom alamanc', () => {
118+
class CapitalAlmanac extends Almanac {
119+
factValue (factId, params, path) {
120+
return super.factValue(factId, params, path).then(value => {
121+
if (typeof value === 'string') {
122+
return value.toUpperCase()
123+
}
124+
return value
125+
})
126+
}
127+
}
128+
129+
it('returns the capitalized value when using the CapitalAlamanc', () => {
130+
return engine.run({ greeting: 'hello', age: 30 }, { almanac: new CapitalAlmanac() }).then((results) => {
131+
const fact = results.almanac.factValue('greeting')
132+
return expect(fact).to.eventually.equal('HELLO')
133+
})
134+
})
135+
})
116136
})

types/index.d.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
export interface EngineOptions {
1+
export interface AlmanacOptions {
22
allowUndefinedFacts?: boolean;
3+
pathResolver?: PathResolver;
4+
}
5+
6+
export interface EngineOptions extends AlmanacOptions {
37
allowUndefinedConditions?: boolean;
48
replaceFactsInEventParams?: boolean;
5-
pathResolver?: PathResolver;
9+
}
10+
11+
export interface RunOptions {
12+
almanac?: Almanac;
613
}
714

815
export interface EngineResult {
@@ -48,7 +55,7 @@ export class Engine {
4855
on(eventName: "failure", handler: EventHandler): this;
4956
on(eventName: string, handler: EventHandler): this;
5057

51-
run(facts?: Record<string, any>): Promise<EngineResult>;
58+
run(facts?: Record<string, any>, runOptions?: RunOptions): Promise<EngineResult>;
5259
stop(): this;
5360
}
5461

@@ -66,6 +73,7 @@ export class Operator<A = unknown, B = unknown> {
6673
}
6774

6875
export class Almanac {
76+
constructor(options?: AlmanacOptions);
6977
factValue<T>(
7078
factId: string,
7179
params?: Record<string, any>,

0 commit comments

Comments
 (0)