Skip to content

Commit a41118b

Browse files
authored
Merge pull request #2 from akmjenkins/feature/ci
CI
2 parents c413746 + a056ac2 commit a41118b

File tree

10 files changed

+119
-287
lines changed

10 files changed

+119
-287
lines changed

.github/workflows/test.yaml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Build
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v1
14+
15+
- uses: actions/cache@v2
16+
with:
17+
path: '**/node_modules'
18+
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
19+
20+
- name: Install packages
21+
run: yarn
22+
23+
- name: Run Lint
24+
run: yarn lint
25+
26+
- name: Run Tests
27+
run: yarn test --coverage
28+
29+
- name: Coveralls
30+
uses: coverallsapp/github-action@master
31+
with:
32+
github-token: ${{ secrets.GITHUB_TOKEN }}
33+
34+
- name: Create Build
35+
run: yarn build
36+
37+

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
# JSON Schema Rules Engine
22

3+
[![npm version](https://img.shields.io/npm/v/json-schema-rules-engine)](https://npmjs.org/package/akmjenkins/json-schema-rules-engine)
4+
[![Coverage Status](https://coveralls.io/repos/github/akmjenkins/json-schema-rules-engine/badge.svg)](https://coveralls.io/github/akmjenkins/json-schema-rules-engine)
5+
![Build Status](https://github.com/akmjenkins/json-schema-rules-engine/actions/workflows/test.yaml/badge.svg)
6+
[![Bundle Phobia](https://badgen.net/bundlephobia/minzip/json-schema-rules-engine)](https://bundlephobia.com/result?p=json-schema-rules-engine)
7+
38
A highly configurable rules engine based on [JSON Schema](https://json-schema.org/). Inspired by the popular [JSON rules engine](https://github.com/CacheControl/json-rules-engine).
49

510
_NBD: It actually doesn't **have** to use JSON Schema, but it's suggested_
611

12+
## Why?
13+
14+
Three reasons:
15+
16+
1. Schema validation of a data structure can be used to implement boolean logic
17+
2. Tools for JSON schema are everywhere and support is wide
18+
3. Custom operators (like those in JSON rules engine) aren't sustainable. You can either make a PR for a new operator that may or may not get merged OR you have to take on the ownership in your own codebase of building and maintaining custom operators. With `json-schema-rules-engine`, you can implement new logic immediately whenever the spec is published (thanks to very actively maintained projects like [AJV](https://github.com/ajv-validator/ajv)).
19+
720
## Features
821

922
- Highly configurable - use any type of schema to express your logic (we strongly suggest JSON Schema)

jest.config.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ module.exports = {
44
'^.+\\.ts?$': 'ts-jest',
55
},
66
transformIgnorePatterns: ['node_modules/(?!(node-fetch|fetch-blob)/)'],
7+
collectCoverageFrom: ['./src/*.js'],
78
coverageThreshold: {
89
global: {
9-
branches: 90,
10-
functions: 90,
11-
lines: 90,
10+
branches: 50,
11+
functions: 75,
12+
lines: 75,
1213
},
1314
},
1415
};

package.json

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
11
{
22
"name": "json-schema-rules-engine",
3-
"version": "0.0.1",
3+
"version": "1.0.0",
44
"description": "Rules engine based on JSON Schema",
55
"main": "build/index.js",
66
"author": "Adam Jenkins",
77
"license": "MIT",
88
"browser": "build/bundle.min.js",
9-
"types": "index.d.ts",
9+
"types": "build/index.d.ts",
1010
"keywords": [
1111
"json schema",
1212
"rules engine"
1313
],
1414
"files": [
15-
"build",
16-
"index.d.ts"
15+
"build"
1716
],
1817
"scripts": {
19-
"dist": "yarn lint && yarn test && yarn build",
2018
"clean": "rimraf build",
2119
"build": "yarn clean && yarn babel && rollup -c",
2220
"babel": "babel src -d build --copy-files --no-copy-ignored",
2321
"lint": "eslint src/",
24-
"test:ci": "yarn test --coverage --coverageReporters=text-lcov | coveralls",
2522
"test": "jest"
2623
},
2724
"devDependencies": {
@@ -39,7 +36,6 @@
3936
"babel-jest": "^27.1.0",
4037
"benchmark": "^2.1.4",
4138
"benchmarkjs": "^0.1.8",
42-
"coveralls": "^3.1.1",
4339
"eslint": "^7.32.0",
4440
"eslint-config-prettier": "^8.3.0",
4541
"eslint-import-resolver-typescript": "^2.4.0",

src/engine.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,17 @@ export const createRulesEngine = (
2626
setRules: (next) => (rules = patch(next, rules)),
2727
run: async (context = {}) => {
2828
emit('start', { context, facts, rules, actions });
29-
const execute = createJob(validator, {
30-
...options,
31-
context,
32-
facts,
33-
rules,
34-
actions,
29+
const execute = createJob(
30+
validator,
31+
{
32+
...options,
33+
context,
34+
facts,
35+
rules,
36+
actions,
37+
},
3538
emit,
36-
});
39+
);
3740

3841
const results = await execute();
3942
emit('complete', { context, results });

src/evaluator.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,37 @@
11
export const createEvaluator =
22
(validator, opts, emit, rule) =>
3-
(factMapId) =>
3+
(mapId) =>
44
async ([factName, { params, path, is }]) => {
5-
const onError = (params) => emit('error', { ...params, rule, factMapId });
5+
emit('debug', { type: 'STARTING_FACT', rule, mapId, factName });
6+
const onError = (params) =>
7+
emit('error', { ...params, factName, rule, mapId });
68

79
const fact = opts.facts[factName] || opts.context[factName];
810
try {
911
const value = await (typeof fact === 'function' ? fact(params) : fact);
1012
const resolved = path ? opts.resolver(value, path) : value;
13+
emit('debug', {
14+
type: 'EXECUTED_FACT',
15+
rule,
16+
mapId,
17+
path,
18+
factName,
19+
value,
20+
resolved,
21+
});
1122
try {
1223
const result = await validator(resolved, is);
24+
emit('debug', {
25+
type: 'EVALUATED_FACT',
26+
rule,
27+
mapId,
28+
path,
29+
factName,
30+
value,
31+
resolved,
32+
is,
33+
result,
34+
});
1335
return { factName, ...result, value, resolved };
1436
} catch (error) {
1537
onError({ type: 'FactEvaluationError', path, is, resolved });

src/fact.map.processor.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { createEvaluator } from './evaluator';
33
export const createFactMapProcessor = (validator, opts, emit) => (rule) => {
44
const evaluator = createEvaluator(validator, opts, emit, rule);
55
return async (factMap, id) => {
6+
emit('debug', { type: 'STARTING_FACT_MAP', rule, mapId: id });
7+
68
// flags for if there was an error processing the fact map
79
// and if all evaluations in the fact map passed
810
let error = false;
@@ -18,6 +20,8 @@ export const createFactMapProcessor = (validator, opts, emit) => (rule) => {
1820
return acc;
1921
}, {});
2022

23+
emit('debug', { type: 'FINISHED_FACT_MAP', rule, mapId: id, results });
24+
2125
// return the results in the same form they were passed in
2226
return {
2327
[id]: {

src/index.d.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,28 +60,32 @@ export type JobConstruct = EngineOptions & {
6060
type StartingFactMapEvent = {
6161
type: 'STARTING_FACT_MAP';
6262
rule: string;
63-
index: number;
63+
mapId: string | number;
6464
};
6565

6666
type StartingFactEvent = {
6767
type: 'STARTING_FACT';
6868
rule: string;
69-
index: number;
69+
mapId: string | number;
70+
factName: string;
7071
};
7172

7273
type ExecutedFactEvent = {
7374
type: 'EXECUTED_FACT';
7475
rule: string;
75-
index: number;
76-
params: any;
76+
mapId: string | number;
77+
factName: string;
78+
params?: any;
79+
path?: string;
7780
value: any;
7881
resolved: any;
7982
};
8083

8184
type EvaluatedFactEvent = {
8285
type: 'EVALUATED_FACT';
8386
rule: string;
84-
index: number;
87+
mapId: string | number;
88+
factName: string;
8589
value: any;
8690
resolved: any;
8791
is: Record<string, any>;
@@ -91,6 +95,8 @@ type EvaluatedFactEvent = {
9195
type FactEvaluationError = {
9296
type: 'FactEvaluationError';
9397
rule: string;
98+
mapId: string | number;
99+
factName: string;
94100
error: Error;
95101
context: Context;
96102
factName: string;
@@ -103,7 +109,8 @@ type FactEvaluationError = {
103109
type FactExecutionError = {
104110
type: 'FactExecutionError';
105111
rule: string;
106-
index: number;
112+
mapId: string | number;
113+
factName: string;
107114
error: Error;
108115
context: Context;
109116
factName: string;

src/rule.runner.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ export const createRuleRunner = (validator, opts, emit) => {
1414
opts.resolver,
1515
);
1616

17+
const process = processor(rule);
18+
1719
const ruleResults = await Promise.all(
1820
Array.isArray(interpolated)
19-
? interpolated.map(processor(rule))
21+
? interpolated.map(process)
2022
: Object.entries(interpolated).map(([factMap, id]) =>
21-
processor(rule)(factMap, id),
23+
process(factMap, id),
2224
),
2325
);
2426

0 commit comments

Comments
 (0)