Skip to content

Commit c413746

Browse files
committed
Get rid of factMapId
1 parent 7a4218d commit c413746

File tree

5 files changed

+24
-15
lines changed

5 files changed

+24
-15
lines changed

README.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ const engine = createRulesEngine({ actions: saveAuditRecord });
190190

191191
### Rules
192192

193-
Rules are written as **when**, **then**, **otherwise**. A when clause consists of an array of [`FactMap`s](#factmap). If any of the `FactMap`s evaluate to true, the properties of the `then` clause of the rule are evaluated. If not, the `otherwise` clause is evaluated.
193+
Rules are written as **when**, **then**, **otherwise**. A when clause consists of an array of [`FactMap`s](#factmap), or an object whose values are [`FactMap`s](#factmap). If any of the `FactMap`s in the object or array evaluate to true, the properties of the `then` clause of the rule are evaluated. If not, the `otherwise` clause is evaluated.
194194

195195
```js
196196
const myRule = {
@@ -235,7 +235,6 @@ The `then` or `otherwise` property can consist of either `actions`, but it can a
235235
const myRule = {
236236
when: [
237237
{
238-
factMapId: 'weatherCondition',
239238
weather: {
240239
params: {
241240
query: '{{city}}',
@@ -256,7 +255,7 @@ const myRule = {
256255
forecast: {
257256
params: {
258257
appId: '{{apiKey}}',
259-
coord: '{{results.weatherCondition.weather.value.coord}}' // interpolate a value returned from the first fact
258+
coord: '{{results[0].weather.value.coord}}' // interpolate a value returned from the first fact
260259
},
261260
path: 'daily',
262261
is: {
@@ -304,8 +303,6 @@ const myRule = {
304303

305304
A FactMap is a plain object whose keys are facts (static or functional) and values are [`Evaluator`'s](#evaluator).
306305

307-
**NOTE: `factMapId` is a reserved word in a `FactMap`. It is used internally to allow easy access to the results of a `FactMap` for interpolation in the `then` or `otherwise` clauses. For this reason `factMapId` _CANNOT_ be given as a fact or context**.
308-
309306
#### Evaluator
310307

311308
An evaluator is an object that specifies a JSON Schema to evaluate a fact against. If the fact is a functional fact, the evaluator can specify params to pass to the fact as an argument. A `path` can also be specified to more easily evaluate a nested property contained within the fact.

src/fact.map.processor.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,14 @@ import { createEvaluator } from './evaluator';
22

33
export const createFactMapProcessor = (validator, opts, emit) => (rule) => {
44
const evaluator = createEvaluator(validator, opts, emit, rule);
5-
return async (factMap, index) => {
6-
// factMapId is a reserved word
7-
const { factMapId = index, ...map } = factMap;
8-
5+
return async (factMap, id) => {
96
// flags for if there was an error processing the fact map
107
// and if all evaluations in the fact map passed
118
let error = false;
129
let passed = true;
1310

1411
const results = (
15-
await Promise.all(Object.entries(map).map(evaluator(factMapId)))
12+
await Promise.all(Object.entries(factMap).map(evaluator(id)))
1613
).reduce((acc, { factName, ...rest }) => {
1714
if (error) return acc;
1815
error = error || !!rest.error;
@@ -23,7 +20,7 @@ export const createFactMapProcessor = (validator, opts, emit) => (rule) => {
2320

2421
// return the results in the same form they were passed in
2522
return {
26-
[factMapId]: {
23+
[id]: {
2724
...results,
2825
__passed: passed,
2926
__error: error,

src/index.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export type Facts = Record<string, UnaryFunction>;
44
export type Actions = Record<string, UnaryFunction>;
55
export type Rules = Record<string, Rule>;
66
export type Rule = {
7-
when: FactMap[];
7+
when: FactMap[] | NamedFactMap;
88
then?: RuleActions | Rule | (Rule & RuleActions);
99
otherwise?: RuleActions | Rule | (Rule & RuleActions);
1010
};
@@ -20,6 +20,10 @@ type RuleActions = {
2020
actions: Action[];
2121
};
2222

23+
interface NamedFactMap {
24+
[named: string]: FactMap;
25+
}
26+
2327
interface FactMap {
2428
[fact: string]: Evaluator;
2529
}

src/rule.runner.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,20 @@ export const createRuleRunner = (validator, opts, emit) => {
66
const processor = createFactMapProcessor(validator, opts, emit);
77
const executor = createActionExecutor(opts, emit);
88
return async ([rule, { when, ...rest }]) => {
9+
// interpolated can be an array FactMap[] OR an object NamedFactMap
10+
const interpolated = interpolateDeep(
11+
when,
12+
opts.context,
13+
opts.pattern,
14+
opts.resolver,
15+
);
16+
917
const ruleResults = await Promise.all(
10-
interpolateDeep(when, opts.context, opts.pattern, opts.resolver).map(
11-
processor(rule),
12-
),
18+
Array.isArray(interpolated)
19+
? interpolated.map(processor(rule))
20+
: Object.entries(interpolated).map(([factMap, id]) =>
21+
processor(rule)(factMap, id),
22+
),
1323
);
1424

1525
// create the context and evaluate whether the rules have passed or errored in a single loop

test/engine.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ describe('rules engine', () => {
3434
await engine.run({ firstName: 'John' });
3535
expect(log).toHaveBeenCalledWith({ message: 'Hi friend!' });
3636
log.mockClear();
37+
await engine.run({ firstName: 'Bill' });
3738
expect(log).not.toHaveBeenCalled();
3839
expect(call).toHaveBeenCalledWith({ message: 'Who are you?' });
3940
});

0 commit comments

Comments
 (0)