@gorules/zen-engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +169 -0
  3. package/index.d.ts +24 -0
  4. package/index.js +252 -0
  5. package/package.json +89 -0
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2023 GoRules.io
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
4
+ files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy,
5
+ modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
6
+ is furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
+
10
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
11
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
12
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
13
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,169 @@
1
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
2
+
3
+ ## NodeJS ZEN Engine
4
+
5
+ ZEN Engine is business friendly Open-Source Business Rules Engine (BRE) to execute decision models according to the [GoRules JSON Decision Model (JDM)](https://gorules.io/docs/rules-engine/json-decision-model) standard. It is written in **Rust** and provides native bindings for **NodeJS** and **Python**. ZEN Engine allows to load and execute JSON Decision Model (JDM) from JSON files.
6
+
7
+ ## Usage
8
+ ZEN Engine is built as embeddable BRE for your **Rust**, **NodeJS** or **Python** applications.
9
+ It parses JDM from JSON content. It is up to you to obtain the JSON content, e.g. from file system, database or service call.
10
+
11
+ If you are looking for a **Decision-as-a-Service** (DaaS) over REST, take a look at [GoRules Cloud](https://gorules.io).
12
+
13
+ ### Installation
14
+ ```bash
15
+ npm i @gorules/zen-engine
16
+ ```
17
+
18
+ or
19
+
20
+ ```bash
21
+ yarn add @gorules/zen-engine
22
+ ```
23
+
24
+ ### Simple Example
25
+ To execute a simple decision you can use the code below.
26
+
27
+ ```typescript
28
+ import { ZenEngine } from "@gorules/zen-engine";
29
+ import fs from 'fs/promises';
30
+
31
+ (async () => {
32
+ // Example filesystem content, it is up to you how you obtain content
33
+ const content = await fs.readFile('./jdm_graph.json');
34
+ const engine = new ZenEngine();
35
+
36
+ const decision = engine.createDecision(content);
37
+ const result = await decision.evaluate({input: 15});
38
+ })();
39
+ ```
40
+
41
+ ### Loaders
42
+
43
+ For more advanced use cases where you want to load multiple decisions and utilise graphs you can build loaders.
44
+
45
+ ```typescript
46
+ import { ZenEngine } from "../index";
47
+ import fs from 'fs/promises';
48
+ import path from 'path';
49
+
50
+ const dataRoot = path.join(__dirname, 'jdm_directory');
51
+
52
+ const loader = async (key: string) => fs.readFile(path.join(testDataRoot, key))
53
+
54
+ (async () => {
55
+ const engine = new ZenEngine({
56
+ loader
57
+ });
58
+
59
+ const result = await engine.evaluate('jdm_graph1.json', {input: 5});
60
+ })();
61
+ ```
62
+ When engine.evaluate is invoked it will call loader and pass a key expecting a content of the JDM decision graph.
63
+ In the case above we will assume file `jdm_directory/jdm_graph1.json` exists.
64
+
65
+ Similar to this example you can also utilise loader to load from different places, for example from REST API, from S3, Database, etc.
66
+
67
+
68
+ ## JSON Decision Model (JDM)
69
+
70
+ JDM is a modeling standard for business decisions and business rules and is stored in a JSON format. Decision models are represented by graphs. Graphs are built using nodes and edges. Edges are used to pass the data from one node to another (left-side to right-side).
71
+
72
+ You can try [Free Online Editor](https://editor.gorules.io) with built in Simulator.
73
+
74
+ <img width="1258" alt="JSON Decision Model" src="https://user-images.githubusercontent.com/60513195/224425568-4a717e34-3d4b-4cc6-b031-8cd35f8ff459.png">
75
+
76
+ [JSON Example](https://github.com/gorules/zen/blob/master/test-data/credit-analysis.json)
77
+
78
+ Input node contains all data sent in the context, and Output node returns the data from the decision. Data flows from the Input Node towards Output Node evaluating all the Nodes in between and passing the data where nodes are connected.
79
+
80
+ ### Decision Tables
81
+ Decision table is a node which allows business users to easily modify and add new rules in an intuitive way using spreadsheet like interface. The structure of decision table is defined by its schema. Schema consists of inputs and outputs.
82
+
83
+ Decision tables are evaluated row by row from top to bottom, and depending on the hit policy a result is calculated.
84
+
85
+ **Inputs**
86
+
87
+ Input fields are commonly (qualified) names for example `cart.total` or `customer.country`.
88
+
89
+ ```json
90
+ {
91
+ "cart": {
92
+ "total": 1000
93
+ },
94
+ "customer": {
95
+ "country": "US"
96
+ }
97
+ }
98
+ ```
99
+
100
+ Inputs are using business-friendly ZEN Expression Language. The language is designed to follow these principles:
101
+
102
+ * Side-effect free
103
+ * Dynamic types
104
+ * Simple syntax for broad audiences
105
+
106
+ List shows basic example of the unary tests in the Input Fields:
107
+
108
+ | Input entry | Input Expression |
109
+ | ---------|-----------|
110
+ | "A" | the field equals "A" |
111
+ | "A", "B" | the field is either "A" or "B"
112
+ | 36 | the numeric value equals 36 |
113
+ | < 36 | a value less than 36 |
114
+ | > 36 | a value greater than 36 |
115
+ | [20..39] | a value between 20 and 39 (inclusive) |
116
+ | 20,39 | a value either 20 or 39 |
117
+ | <20, >39 | a value either less than 20 or greater than 39|
118
+ | true | the boolean value true |
119
+ | false | the boolean value false |
120
+ | | any value, even null/undefined |
121
+ | null | the value null or undefined |
122
+
123
+ Note: For the full list please visit [ZEN Expression Language](https://gorules.io/docs/rules-engine/expression-language/).
124
+
125
+ **Outputs**
126
+
127
+ The result of the decisionTableNode evaluation is:
128
+
129
+ * an object if the hit policy of the decision table is FIRST and a rule matched. The structure is defined by the output fields. Qualified field names with a dot (.) inside lead to nested objects.
130
+ * `null`/`undefined` if no rule matched in FIRST hit policy
131
+ * an array of objects if the hit policy of the decision table is COLLECT (one array item for each matching rule) or empty array if no rules match
132
+
133
+ Example:
134
+
135
+ <img width="860" alt="Screenshot 2023-03-10 at 22 57 04" src="https://user-images.githubusercontent.com/60513195/224436208-a2266cec-d0c6-42c7-8607-a4071e6a950b.png">
136
+
137
+ And the result would be:
138
+
139
+ ```json
140
+ {
141
+ "flatProperty": "A",
142
+ "output": {
143
+ "nested": {
144
+ "property": "B"
145
+ },
146
+ "property": 36
147
+ }
148
+ }
149
+ ```
150
+
151
+ ### Functions
152
+ Function nodes are JavaScript lambdas that allow for quick and easy parsing, re-mapping or otherwise modifying the data. Inputs of the node are provided as function's arguments. Functions are executed on top of Google's V8 Engine that is built in into the ZEN Engine.
153
+
154
+ ```js
155
+ const handler = (input) => {
156
+ return input;
157
+ }
158
+ ```
159
+
160
+ ### Decision
161
+ Decision is a special node whose purpose is for decision model to have an ability to call other/re-usable decision models during an execution.
162
+
163
+ ## Support matrix
164
+
165
+ linux-x64-gnu | linux-arm64-gnu | darvin-x64 | darvin-arm64 | win32-x64-msvc
166
+ :------------ |:------------------- |:------------------- |:------------------- |:-------------------
167
+ yes | yes | yes | yes | yes
168
+
169
+ We do not support linux-musl for now as we are relying on the GoogleV8 engine to run function blocks as isolates.
package/index.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /* auto-generated by NAPI-RS */
5
+
6
+ export interface ZenEvaluateOptions {
7
+ maxDepth?: number
8
+ trace?: boolean
9
+ }
10
+ export interface JsZenEngineOptions {
11
+ loader?: (key: string) => Promise<Buffer>
12
+ }
13
+ export type JsZenDecision = ZenDecision
14
+ export class ZenDecision {
15
+ constructor()
16
+ evaluate(context: any, opts?: JsZenEvaluateOptions | undefined | null): Promise<any>
17
+ }
18
+ export type JsZenEngine = ZenEngine
19
+ export class ZenEngine {
20
+ constructor(options?: JsZenEngineOptions | undefined | null)
21
+ evaluate(key: string, context: any, opts?: ZenEvaluateOptions | undefined | null): Promise<any>
22
+ createDecision(content: Buffer): ZenDecision
23
+ getDecision(key: string): Promise<ZenDecision>
24
+ }
package/index.js ADDED
@@ -0,0 +1,252 @@
1
+ const { existsSync, readFileSync } = require('fs')
2
+ const { join } = require('path')
3
+
4
+ const { platform, arch } = process
5
+
6
+ let nativeBinding = null
7
+ let localFileExisted = false
8
+ let loadError = null
9
+
10
+ function isMusl() {
11
+ // For Node 10
12
+ if (!process.report || typeof process.report.getReport !== 'function') {
13
+ try {
14
+ const lddPath = require('child_process').execSync('which ldd').toString().trim();
15
+ return readFileSync(lddPath, 'utf8').includes('musl')
16
+ } catch (e) {
17
+ return true
18
+ }
19
+ } else {
20
+ const { glibcVersionRuntime } = process.report.getReport().header
21
+ return !glibcVersionRuntime
22
+ }
23
+ }
24
+
25
+ switch (platform) {
26
+ case 'android':
27
+ switch (arch) {
28
+ case 'arm64':
29
+ localFileExisted = existsSync(join(__dirname, 'zen-engine.android-arm64.node'))
30
+ try {
31
+ if (localFileExisted) {
32
+ nativeBinding = require('./zen-engine.android-arm64.node')
33
+ } else {
34
+ nativeBinding = require('@gorules/zen-engine-android-arm64')
35
+ }
36
+ } catch (e) {
37
+ loadError = e
38
+ }
39
+ break
40
+ case 'arm':
41
+ localFileExisted = existsSync(join(__dirname, 'zen-engine.android-arm-eabi.node'))
42
+ try {
43
+ if (localFileExisted) {
44
+ nativeBinding = require('./zen-engine.android-arm-eabi.node')
45
+ } else {
46
+ nativeBinding = require('@gorules/zen-engine-android-arm-eabi')
47
+ }
48
+ } catch (e) {
49
+ loadError = e
50
+ }
51
+ break
52
+ default:
53
+ throw new Error(`Unsupported architecture on Android ${arch}`)
54
+ }
55
+ break
56
+ case 'win32':
57
+ switch (arch) {
58
+ case 'x64':
59
+ localFileExisted = existsSync(
60
+ join(__dirname, 'zen-engine.win32-x64-msvc.node')
61
+ )
62
+ try {
63
+ if (localFileExisted) {
64
+ nativeBinding = require('./zen-engine.win32-x64-msvc.node')
65
+ } else {
66
+ nativeBinding = require('@gorules/zen-engine-win32-x64-msvc')
67
+ }
68
+ } catch (e) {
69
+ loadError = e
70
+ }
71
+ break
72
+ case 'ia32':
73
+ localFileExisted = existsSync(
74
+ join(__dirname, 'zen-engine.win32-ia32-msvc.node')
75
+ )
76
+ try {
77
+ if (localFileExisted) {
78
+ nativeBinding = require('./zen-engine.win32-ia32-msvc.node')
79
+ } else {
80
+ nativeBinding = require('@gorules/zen-engine-win32-ia32-msvc')
81
+ }
82
+ } catch (e) {
83
+ loadError = e
84
+ }
85
+ break
86
+ case 'arm64':
87
+ localFileExisted = existsSync(
88
+ join(__dirname, 'zen-engine.win32-arm64-msvc.node')
89
+ )
90
+ try {
91
+ if (localFileExisted) {
92
+ nativeBinding = require('./zen-engine.win32-arm64-msvc.node')
93
+ } else {
94
+ nativeBinding = require('@gorules/zen-engine-win32-arm64-msvc')
95
+ }
96
+ } catch (e) {
97
+ loadError = e
98
+ }
99
+ break
100
+ default:
101
+ throw new Error(`Unsupported architecture on Windows: ${arch}`)
102
+ }
103
+ break
104
+ case 'darwin':
105
+ localFileExisted = existsSync(join(__dirname, 'zen-engine.darwin-universal.node'))
106
+ try {
107
+ if (localFileExisted) {
108
+ nativeBinding = require('./zen-engine.darwin-universal.node')
109
+ } else {
110
+ nativeBinding = require('@gorules/zen-engine-darwin-universal')
111
+ }
112
+ break
113
+ } catch {}
114
+ switch (arch) {
115
+ case 'x64':
116
+ localFileExisted = existsSync(join(__dirname, 'zen-engine.darwin-x64.node'))
117
+ try {
118
+ if (localFileExisted) {
119
+ nativeBinding = require('./zen-engine.darwin-x64.node')
120
+ } else {
121
+ nativeBinding = require('@gorules/zen-engine-darwin-x64')
122
+ }
123
+ } catch (e) {
124
+ loadError = e
125
+ }
126
+ break
127
+ case 'arm64':
128
+ localFileExisted = existsSync(
129
+ join(__dirname, 'zen-engine.darwin-arm64.node')
130
+ )
131
+ try {
132
+ if (localFileExisted) {
133
+ nativeBinding = require('./zen-engine.darwin-arm64.node')
134
+ } else {
135
+ nativeBinding = require('@gorules/zen-engine-darwin-arm64')
136
+ }
137
+ } catch (e) {
138
+ loadError = e
139
+ }
140
+ break
141
+ default:
142
+ throw new Error(`Unsupported architecture on macOS: ${arch}`)
143
+ }
144
+ break
145
+ case 'freebsd':
146
+ if (arch !== 'x64') {
147
+ throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
148
+ }
149
+ localFileExisted = existsSync(join(__dirname, 'zen-engine.freebsd-x64.node'))
150
+ try {
151
+ if (localFileExisted) {
152
+ nativeBinding = require('./zen-engine.freebsd-x64.node')
153
+ } else {
154
+ nativeBinding = require('@gorules/zen-engine-freebsd-x64')
155
+ }
156
+ } catch (e) {
157
+ loadError = e
158
+ }
159
+ break
160
+ case 'linux':
161
+ switch (arch) {
162
+ case 'x64':
163
+ if (isMusl()) {
164
+ localFileExisted = existsSync(
165
+ join(__dirname, 'zen-engine.linux-x64-musl.node')
166
+ )
167
+ try {
168
+ if (localFileExisted) {
169
+ nativeBinding = require('./zen-engine.linux-x64-musl.node')
170
+ } else {
171
+ nativeBinding = require('@gorules/zen-engine-linux-x64-musl')
172
+ }
173
+ } catch (e) {
174
+ loadError = e
175
+ }
176
+ } else {
177
+ localFileExisted = existsSync(
178
+ join(__dirname, 'zen-engine.linux-x64-gnu.node')
179
+ )
180
+ try {
181
+ if (localFileExisted) {
182
+ nativeBinding = require('./zen-engine.linux-x64-gnu.node')
183
+ } else {
184
+ nativeBinding = require('@gorules/zen-engine-linux-x64-gnu')
185
+ }
186
+ } catch (e) {
187
+ loadError = e
188
+ }
189
+ }
190
+ break
191
+ case 'arm64':
192
+ if (isMusl()) {
193
+ localFileExisted = existsSync(
194
+ join(__dirname, 'zen-engine.linux-arm64-musl.node')
195
+ )
196
+ try {
197
+ if (localFileExisted) {
198
+ nativeBinding = require('./zen-engine.linux-arm64-musl.node')
199
+ } else {
200
+ nativeBinding = require('@gorules/zen-engine-linux-arm64-musl')
201
+ }
202
+ } catch (e) {
203
+ loadError = e
204
+ }
205
+ } else {
206
+ localFileExisted = existsSync(
207
+ join(__dirname, 'zen-engine.linux-arm64-gnu.node')
208
+ )
209
+ try {
210
+ if (localFileExisted) {
211
+ nativeBinding = require('./zen-engine.linux-arm64-gnu.node')
212
+ } else {
213
+ nativeBinding = require('@gorules/zen-engine-linux-arm64-gnu')
214
+ }
215
+ } catch (e) {
216
+ loadError = e
217
+ }
218
+ }
219
+ break
220
+ case 'arm':
221
+ localFileExisted = existsSync(
222
+ join(__dirname, 'zen-engine.linux-arm-gnueabihf.node')
223
+ )
224
+ try {
225
+ if (localFileExisted) {
226
+ nativeBinding = require('./zen-engine.linux-arm-gnueabihf.node')
227
+ } else {
228
+ nativeBinding = require('@gorules/zen-engine-linux-arm-gnueabihf')
229
+ }
230
+ } catch (e) {
231
+ loadError = e
232
+ }
233
+ break
234
+ default:
235
+ throw new Error(`Unsupported architecture on Linux: ${arch}`)
236
+ }
237
+ break
238
+ default:
239
+ throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
240
+ }
241
+
242
+ if (!nativeBinding) {
243
+ if (loadError) {
244
+ throw loadError
245
+ }
246
+ throw new Error(`Failed to load native binding`)
247
+ }
248
+
249
+ const { ZenDecision, ZenEngine } = nativeBinding
250
+
251
+ module.exports.ZenDecision = ZenDecision
252
+ module.exports.ZenEngine = ZenEngine
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@gorules/zen-engine",
3
+ "version": "0.1.0",
4
+ "main": "index.js",
5
+ "types": "./index.d.ts",
6
+ "license": "MIT",
7
+ "typings": "index.d.ts",
8
+ "files": [
9
+ "index.js",
10
+ "index.d.ts",
11
+ "README.md"
12
+ ],
13
+ "napi": {
14
+ "name": "zen-engine",
15
+ "triples": {
16
+ "defaults": false,
17
+ "additional": [
18
+ "x86_64-apple-darwin",
19
+ "x86_64-unknown-linux-gnu",
20
+ "x86_64-pc-windows-msvc",
21
+ "aarch64-unknown-linux-gnu",
22
+ "aarch64-apple-darwin"
23
+ ]
24
+ },
25
+ "npmClient": "yarn"
26
+ },
27
+ "keywords": [
28
+ "gorules",
29
+ "zen-engine",
30
+ "business rules engine",
31
+ "rules engine",
32
+ "rule engine",
33
+ "bre",
34
+ "rule",
35
+ "rules",
36
+ "engine",
37
+ "decision",
38
+ "decision table",
39
+ "rust",
40
+ "N-API",
41
+ "napi-rs",
42
+ "node-rs"
43
+ ],
44
+ "author": "GoRules <hi@gorules.io> (https://gorules.io)",
45
+ "homepage": "https://github.com/gorules/zen",
46
+ "engines": {
47
+ "node": ">= 14"
48
+ },
49
+ "devDependencies": {
50
+ "@jest/globals": "^29.5.0",
51
+ "@napi-rs/cli": "^2.14.8",
52
+ "@types/express": "^4.17.17",
53
+ "@types/node": "^18.15.0",
54
+ "babel-jest": "^29.5.0",
55
+ "express": "^4.18.2",
56
+ "jest": "^29.5.0",
57
+ "lerna": "^6.5.1",
58
+ "ts-jest": "^29.0.5",
59
+ "ts-node": "^10.9.1",
60
+ "tslib": "^2.5.0",
61
+ "typescript": "^4.9.5"
62
+ },
63
+ "bugs": {
64
+ "url": "https://github.com/gorules/zen/issues"
65
+ },
66
+ "publishConfig": {
67
+ "registry": "https://registry.npmjs.org/"
68
+ },
69
+ "repository": {
70
+ "type": "git",
71
+ "url": "git+https://github.com/gorules/zen.git"
72
+ },
73
+ "scripts": {
74
+ "build": "napi build --platform --release --js index.js --dts index.d.ts",
75
+ "build:debug": "napi build --platform --js index.js --dts index.d.ts",
76
+ "test": "jest --forceExit",
77
+ "artifacts": "napi artifacts -d ../../artifacts",
78
+ "prepublishOnly": "napi prepublish",
79
+ "version": "napi version"
80
+ },
81
+ "gitHead": "b1a1242f04528a47cb4dfd83b4648ded9468dc89",
82
+ "optionalDependencies": {
83
+ "@gorules/zen-engine-darwin-x64": "0.1.0",
84
+ "@gorules/zen-engine-linux-x64-gnu": "0.1.0",
85
+ "@gorules/zen-engine-win32-x64-msvc": "0.1.0",
86
+ "@gorules/zen-engine-linux-arm64-gnu": "0.1.0",
87
+ "@gorules/zen-engine-darwin-arm64": "0.1.0"
88
+ }
89
+ }