fix: improve READMEs across bindings (#516)

* fix: improve READMEs across bindings

* add readmes for crates
This commit is contained in:
stefan-gorules
2026-08-22 13:10:37 +02:00
committed by GitHub
parent f4e32deb9a
commit 692ca2fe6b
19 changed files with 514 additions and 633 deletions
+76 -270
View File
@@ -1,321 +1,127 @@
# Node.js Rules Engine
**Business logic humans can read and machines can run.** One copy of your rules: the owner reads it, every system runs it.
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![npm](https://img.shields.io/npm/v/@gorules/zen-engine.svg)](https://www.npmjs.com/package/@gorules/zen-engine)
# NodeJS Rules Engine
<img width="1280" alt="GoRules ZEN Engine" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/hero.png">
ZEN Engine is a cross-platform, Open-Source Business Rules Engine (BRE). It is written in **Rust** and provides native bindings for **NodeJS**, **Python** and **Go**. ZEN Engine allows to load and execute [JSON Decision Model (JDM)](https://gorules.io/docs/rules-engine/json-decision-model) from JSON files.
ZEN Engine is a cross-platform, open-source [Business Rules Engine (BRE)](https://gorules.io) written in **Rust** with native **Node.js** bindings, alongside Python, Go, Java, Kotlin and .NET. Decisions evaluate in microseconds, run identically on every platform, and are stored as portable JSON. Loading the JSON is up to you: file system, database or service call.
<img width="800" alt="Open-Source Rules Engine" src="https://gorules.io/images/jdm-editor.gif">
Try it in the free [Online Editor](https://editor.gorules.io) with a built-in simulator, or embed the open-source React [JDM Editor](https://github.com/gorules/jdm-editor) in your own product. Learn more about the [Node.js rules engine](https://gorules.io/open-source/javascript-rules-engine) on the GoRules website.
An open-source React editor is available on our [JDM Editor](https://github.com/gorules/jdm-editor) repo.
## Rules that read like sentences
## Usage
Conditions are written the way the business says them, in the ZEN Expression Language. The developer view is one toggle away, and the two can never drift apart: there is only one source of truth, and this engine runs it.
ZEN Engine is built as embeddable BRE for your **Rust**, **NodeJS**, **Python** or **Go** applications.
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.
<img width="1280" alt="Readable rules" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/tables.png">
### Installation
## Rules as graphs, or as documents
Model a decision on a visual canvas of decision tables, switches, expressions, functions and reusable sub-decisions. Or write it as a policy document with prose, typed data models and tables. Both compile to the same engine and return the same answers.
<img width="1280" alt="Graphs and documents" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/graphs-docs.png">
To go deeper, see the [Node.js SDK documentation](https://docs.gorules.io/developers/sdks/nodejs), the [decision graph guide](https://docs.gorules.io/learn/authoring/decision-graphs) and the [ZEN Expression Language](https://docs.gorules.io/learn/zen-language/syntax) reference.
## What's new in 2.0
Version 2.0 is the first stable release of the new engine line:
- **Policy documents**: model decisions as readable documents with typed data models, expressions, decision tables, match blocks and assertions. Policies compile to the same engine as graphs and return the same answers.
- **Workspace analysis**: static type checking across policies and graphs. Type flow, exhaustiveness checking, write-conflict detection and precise diagnostics, all available before anything runs.
- **Per-column collect**: decision table output columns can collect across all matching rows (`tags[]`) while the rest of the table stays first-match.
- **Pre-compiled engine**: decisions are parsed and compiled once at load; evaluation is allocation-light and repeat-safe.
- **Hardened runtime**: out-of-range numbers, arithmetic overflow and malformed inputs return errors or nulls instead of crashing the process.
- **Unified bindings**: configurable loaders, batch evaluation and consistent error envelopes across Node.js, Python, Go and FFI consumers.
## Installation
```bash
npm i @gorules/zen-engine
```
or
Prebuilt binaries ship for Linux (gnu and musl), macOS, Windows and WASM (WASI); no Rust toolchain required.
```bash
yarn add @gorules/zen-engine
```
### Simple Example
To execute a simple decision you can use the code below.
## Quickstart
```typescript
import { ZenEngine } from '@gorules/zen-engine';
import fs from 'fs/promises';
(async () => {
// Example filesystem content, it is up to you how you obtain content
const content = await fs.readFile('./jdm_graph.json');
const engine = new ZenEngine();
const content = await fs.readFile('./jdm_graph.json');
const engine = new ZenEngine();
const decision = engine.createDecision(content);
const result = await decision.evaluate({ input: 15 });
})();
const decision = engine.createDecision(content);
const result = await decision.evaluate({ input: 15 });
```
### Loaders
For more advanced use cases where you want to load multiple decisions and utilise graphs you can build loaders.
For more advanced use cases where you want to load multiple decisions and reuse them across evaluations you can build loaders. When `engine.evaluate` is invoked it calls the loader with a key, expecting the content of the JDM decision graph in return.
```typescript
import { ZenEngine } from '../index';
import { ZenEngine } from '@gorules/zen-engine';
import fs from 'fs/promises';
import path from 'path';
const dataRoot = path.join(__dirname, 'jdm_directory');
const loader = async (key: string) => fs.readFile(path.join(__dirname, 'jdm_directory', key));
const loader = async (key: string) => fs.readFile(path.join(testDataRoot, key))(async () => {
const engine = new ZenEngine({ loader });
const result = await engine.evaluate('jdm_graph1.json', { input: 5 });
})();
const engine = new ZenEngine({ loader });
const result = await engine.evaluate('jdm_graph1.json', { input: 5 });
```
When engine.evaluate is invoked it will call loader and pass a key expecting a content of the JDM decision graph.
In the case above we will assume file `jdm_directory/jdm_graph1.json` exists.
The same pattern works for loading from a REST API, S3, a database, or anywhere else. Full guides, including multi-decision graphs and batch evaluation, are in the [Node.js SDK documentation](https://docs.gorules.io/developers/sdks/nodejs).
Similar to this example you can also utilise loader to load from different places, for example from REST API, from S3, Database, etc.
## Other platforms
### Supported Platforms
* **Node.js** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/nodejs) | [Documentation](https://docs.gorules.io/developers/sdks/nodejs) | [npm](https://www.npmjs.com/package/@gorules/zen-engine)
* **Python** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/python) | [Documentation](https://docs.gorules.io/developers/sdks/python) | [PyPI](https://pypi.org/project/zen-engine/)
* **Go** - [GitHub](https://github.com/gorules/zen-go) | [Documentation](https://docs.gorules.io/developers/sdks/go)
* **Java / Kotlin** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/uniffi) | [Documentation](https://docs.gorules.io/developers/sdks/java) | [Maven Central](https://mvnrepository.com/artifact/io.gorules/zen-engine)
* **.NET** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/uniffi) | [Documentation](https://docs.gorules.io/developers/sdks/csharp) | [NuGet](https://www.nuget.org/packages/GoRules.ZenEngine)
* **Rust (Core)** - [GitHub](https://github.com/gorules/zen) | [Documentation](https://docs.gorules.io/developers/sdks/rust) | [crates.io](https://crates.io/crates/zen-engine)
List of platforms where Zen Engine is natively available:
## The GoRules platform
* **NodeJS** - [GitHub](https://github.com/gorules/zen/blob/master/bindings/nodejs/README.md) | [Documentation](https://gorules.io/docs/developers/bre/engines/nodejs) | [npmjs](https://www.npmjs.com/package/@gorules/zen-engine)
* **Python** - [GitHub](https://github.com/gorules/zen/blob/master/bindings/python/README.md) | [Documentation](https://gorules.io/docs/developers/bre/engines/python) | [pypi](https://pypi.org/project/zen-engine/)
* **Go** - [GitHub](https://github.com/gorules/zen-go) | [Documentation](https://gorules.io/docs/developers/bre/engines/go)
* **Rust (Core)** - [GitHub](https://github.com/gorules/zen) | [Documentation](https://gorules.io/docs/developers/bre/engines/rust) | [crates.io](https://crates.io/crates/zen-engine)
The engine is open at the core; [GoRules](https://gorules.io) is the platform around it. Managed cloud, self-hosted, or embedded with no network hop. SOC 2 Type II.
For a complete **Business Rules Management Systems (BRMS)** solution:
### AI that builds rules, and stays reviewable
* [Self-hosted BRMS](https://gorules.io)
* [GoRules Cloud BRMS](https://gorules.io/signin/verify-email)
An AI copilot and MCP server that edits rules, runs tests and explains decisions. It never deploys. Releases stay with your reviewers.
## JSON Decision Model (JDM)
<img width="800" alt="GoRules AI" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/ai.png">
GoRules JDM (JSON Decision Model) is a modeling framework designed to streamline the representation and implementation of decision models.
### Promote like a release, run like a binary
#### Understanding GoRules JDM
At its core, GoRules JDM revolves around the concept of decision models as interconnected graphs stored in JSON format.
These graphs capture the intricate relationships between various decision points, conditions, and outcomes in a GoRules Zen-Engine.
A release moves from testing to staging to production untouched. Approvals, instant rollback, and a paper trail for every change.
Graphs are made by linking nodes with edges, which act like pathways for moving information from one node to another, usually from the left to the right.
<img width="800" alt="Governance" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/governance.png">
The Input node serves as an entry for all data relevant to the context, while the Output nodes produce the result of decision-making process. The progression of data follows a path from the Input Node to the Output Node, traversing all interconnected nodes in between. As the data flows through this network, it undergoes evaluation at each node, and connections determine where the data is passed along the graph.
### Prove it before it ships
To see JDM Graph in action you can use [Free Online Editor](https://editor.gorules.io) with built in Simulator.
Scenario suites run on every change, coverage is measured against decision paths, and every answer comes with a replayable trace.
There are 5 main node types in addition to a graph Input Node (Request) and Output Node (Response):
* Decision Table Node
* Switch Node
* Function Node
* Expression Node
* Decision Node
### Decision Table Node
#### Overview
Tables provide a structured representation of decision-making processes, allowing developers and business users to express complex rules in a clear and concise manner.
<img width="960" alt="Decision Table" src="https://gorules.io/images/decision-table.png">
#### Structure
At the core of the Decision Table is its schema, defining the structure with inputs and outputs. Inputs encompass business-friendly expressions using the ZEN Expression Language, accommodating a range of conditions such as equality, numeric comparisons, boolean values, date time functions, array functions and more. The schema's outputs dictate the form of results generated by the Decision Table.
Inputs and outputs are expressed through a user-friendly interface, often resembling a spreadsheet. This facilitates easy modification and addition of rules, enabling business users to contribute to decision logic without delving into intricate code.
#### Evaluation Process
Decision Tables are evaluated row by row, from top to bottom, adhering to a specified hit policy.
Single row is evaluated via Inputs columns, from left to right. Each input column represents `AND` operator. If cell is empty that column is evaluated **truthfully**, independently of the value.
If a single cell within a row fails (due to error, or otherwise), the row is skipped.
**HitPolicy**
The hit policy determines the outcome calculation based on matching rules.
The result of the evaluation is:
* **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.
* **`null`/`undefined`** if no rule matched in `first` hit policy
* **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
#### Inputs
In the assessment of rules or rows, input columns embody the `AND` operator. The values typically consist of (qualified) names, such as `customer.country` or `customer.age`.
There are two types of evaluation of inputs, `Unary` and `Expression`.
**Unary Evaluation**
Unary evaluation is usually used when we would like to compare single fields from incoming context separately, for example `customer.country` and `cart.total` . It is activated when a column has `field` defined in its schema.
***Example***
For the input:
```json
{
"customer": {
"country": "US"
},
"cart": {
"total": 1500
}
}
```
<img width="960" alt="Decision Table Unary Test" src="https://gorules.io/images/decision-table.png">
This evaluation translates to
```
IF customer.country == 'US' AND cart.total > 1000 THEN {"fees": {"percent": 2}}
ELSE IF customer.country == 'US' THEN {"fees": {"flat": 30}}
ELSE IF customer.country == 'CA' OR customer.country == 'MX' THEN {"fees": {"flat": 50}}
ELSE {"fees": {"flat": 150}}
```
List shows basic example of the unary tests in the Input Fields:
| Input entry | Input Expression |
| ----------- | ---------------------------------------------- |
| "A" | the field equals "A" |
| "A", "B" | the field is either "A" or "B" |
| 36 | the numeric value equals 36 |
| < 36 | a value less than 36 |
| > 36 | a value greater than 36 |
| [20..39] | a value between 20 and 39 (inclusive) |
| 20,39 | a value either 20 or 39 |
| <20, >39 | a value either less than 20 or greater than 39 |
| true | the boolean value true |
| false | the boolean value false |
| | any value, even null/undefined |
| null | the value null or undefined |
Note: For the full list please visit [ZEN Expression Language](https://gorules.io/docs/rules-engine/expression-language/).
**Expression Evaluation**
Expression evaluation is used when we would like to create more complex evaluation logic inside single cell. It allows us to compare multiple fields from the incoming context inside same cell.
It can be used by providing an empty `Selector (field)` inside column configuration.
***Example***
For the input:
```json
{
"transaction": {
"country": "US",
"createdAt": "2023-11-20T19:00:25Z",
"amount": 10000
}
}
```
<img width="960" alt="Decision Table Expression" src="https://gorules.io/images/decision-table-expression.png">
```
IF time(transaction.createdAt) > time("17:00:00") AND transaction.amount > 1000 THEN {"status": "reject"}
ELSE {"status": "approve"}
```
Note: For the full list please visit [ZEN Expression Language](https://gorules.io/docs/rules-engine/expression-language/).
**Outputs**
Output columns serve as the blueprint for the data that the decision table will generate when the conditions are met during evaluation.
When a row in the decision table satisfies its specified conditions, the output columns determine the nature and structure of the information that will be returned. Each output column represents a distinct field, and the collective set of these fields forms the output or result associated with the validated row. This mechanism allows decision tables to precisely define and control the data output.
***Example***
<img width="860" alt="Decision Table Output" src="https://gorules.io/images/decision-table-output.png">
And the result would be:
```json
{
"flatProperty": "A",
"output": {
"nested": {
"property": "B"
},
"property": 36
}
}
```
### Switch Node (NEW)
The Switch node in GoRules JDM introduces a dynamic branching mechanism to decision models, enabling the graph to diverge based on conditions.
Conditions are written in a Zen Expression Language.
By incorporating the Switch node, decision models become more flexible and context-aware. This capability is particularly valuable in scenarios where diverse decision logic is required based on varying inputs. The Switch node efficiently manages branching within the graph, enhancing the overall complexity and realism of decision models in GoRules JDM, making it a pivotal component for crafting intelligent and adaptive systems.
The Switch node preserves the incoming data without modification; it forwards the entire context to the output branch(es).
<img width="960" alt="Switch / Branching" src="https://gorules.io/images/decision-graph.png">
#### HitPolicy
There are two HitPolicy options for the switch node, `first` and `collect`.
In the context of a first hit policy, the graph branches to the initial matching condition, analogous to the behavior observed in a table. Conversely, under a collect hit policy, the graph extends to all branches where conditions hold true, allowing branching to multiple paths.
Note: If there are multiple edges from the same condition, there is no guaranteed order of execution.
*Available from:*
* Python 0.16.0
* NodeJS 0.13.0
* Rust 0.16.0
* Go 0.1.0
### Functions Node
Function nodes are JavaScript snippets that allow for quick and easy parsing, re-mapping or otherwise modifying the data using JavaScript. Inputs of the node are provided as function's arguments. Functions are executed on top of QuickJS Engine that is bundled into the ZEN Engine.
Function timeout is set to a 50ms.
```js
const handler = (input, {dayjs, Big}) => {
return {
...input,
someField: 'hello'
};
};
```
There are two built in libraries:
* [dayjs](https://www.npmjs.com/package/dayjs) - for Date Manipulation
* [big.js](https://www.npmjs.com/package/big.js) - for arbitrary-precision decimal arithmetic.
### Expression Node
The Expression node serves as a tool for transforming input objects into alternative objects using the Zen Expression Language. When specifying the output properties, each property requires a separate row. These rows are defined by two fields:
- Key - qualified name of the output property
- Value - value expressed through the Zen Expression Language
Note: Any errors within the Expression node will bring the graph to a halt.
<img width="960" alt="Decision Table" src="https://gorules.io/images/expression.png">
### Decision Node
The "Decision" node is designed to extend the capabilities of decision models. Its function is to invoke and reuse other decision models during execution.
By incorporating the "Decision" node, developers can modularize decision logic, promoting reusability and maintainability in complex systems.
<img width="800" alt="Testing" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/tests.png">
## Support matrix
| Arch | Rust | NodeJS | Python | Go |
|:----------------|:-------------------|:-------------------|:-------------------|:-------------------|
| linux-x64-gnu | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| linux-arm64-gnu | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| darwin-x64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| darwin-arm64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| win32-x64-msvc | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| linux-x64-musl | :x: | :heavy_check_mark: | :x: | :x: |
| linux-arm64-musl| :x: | :heavy_check_mark: | :x: | :x: |
| Arch | Node.js |
|:-----------------|:-------------------|
| linux-x64-gnu | :heavy_check_mark: |
| linux-arm64-gnu | :heavy_check_mark: |
| darwin-x64 | :heavy_check_mark: |
| darwin-arm64 | :heavy_check_mark: |
| win32-x64-msvc | :heavy_check_mark: |
| linux-x64-musl | :heavy_check_mark: |
| linux-arm64-musl | :heavy_check_mark: |
| wasm32 (WASI) | :heavy_check_mark: |
## Contribution
JDM standard is growing and we need to keep tight control over its development and roadmap as there are number of
companies that are using GoRules Zen-Engine and GoRules BRMS.
For this reason we can't accept any code contributions at this moment, apart from help with documentation and additional
tests.
The JDM standard is growing and we need to keep tight control over its development and roadmap, as a number of companies use GoRules ZEN Engine and GoRules BRMS. For this reason we can't accept code contributions at this moment, apart from help with documentation and additional tests.
## License
[MIT License](https://opensource.org/licenses/MIT)
+21
View File
@@ -0,0 +1,21 @@
import { readdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
import { join } from 'node:path';
const npmRoot = join(import.meta.dirname, 'npm');
for (const dir of readdirSync(npmRoot)) {
const readmePath = join(npmRoot, dir, 'README.md');
if (!existsSync(readmePath)) continue;
const readme = readFileSync(readmePath, 'utf8');
const target = readme.match(/This is the \*\*(.+?)\*\* binary/)?.[1] ?? dir;
writeFileSync(
readmePath,
`# \`@gorules/zen-engine-${dir}\`
This is the **${target}** binary for [\`@gorules/zen-engine\`](https://www.npmjs.com/package/@gorules/zen-engine), the open-source [Node.js rules engine](https://gorules.io/open-source/javascript-rules-engine) from [GoRules](https://gorules.io).
- [Documentation](https://docs.gorules.io/developers/sdks/nodejs)
- [GitHub](https://github.com/gorules/zen)
`,
);
}
+3 -2
View File
@@ -1,5 +1,6 @@
{
"name": "@gorules/zen-engine",
"description": "Open-source Business Rules Engine for Node.js, powered by Rust",
"version": "2.0.0",
"main": "index.js",
"browser": "browser.js",
@@ -56,7 +57,7 @@
"node-rs"
],
"author": "GoRules <hi@gorules.io> (https://gorules.io)",
"homepage": "https://github.com/gorules/zen",
"homepage": "https://gorules.io",
"engines": {
"node": ">= 14"
},
@@ -94,7 +95,7 @@
"watch": "cargo watch --ignore '{index.js,index.d.ts}' -- npm run build:debug",
"test": "cross-env __ZEN_MOCK_UTC_TIME=2025-08-19T16:55:02.078Z jest",
"artifacts": "napi artifacts -d ../../artifacts",
"createNpmDirs": "napi create-npm-dirs",
"createNpmDirs": "napi create-npm-dirs && node enrich-npm-dirs.mjs",
"prepublishOnly": "napi prepublish --no-gh-release",
"version": "napi version"
},
+67 -250
View File
@@ -1,33 +1,56 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
# Python Rules Engine
ZEN Engine is a cross-platform, Open-Source Business Rules Engine (BRE). It is written in **Rust** and provides native bindings for **NodeJS**, **Python** and **Go**. ZEN Engine allows to load and execute [JSON Decision Model (JDM)](https://gorules.io/docs/rules-engine/json-decision-model) from JSON files.
**Business logic humans can read and machines can run.** One copy of your rules: the owner reads it, every system runs it.
<img width="800" alt="Open-Source Rules Engine" src="https://gorules.io/images/jdm-editor.gif">
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![PyPI](https://img.shields.io/pypi/v/zen-engine.svg)](https://pypi.org/project/zen-engine/)
An open-source React editor is available on our [JDM Editor](https://github.com/gorules/jdm-editor) repo.
<img width="1280" alt="GoRules ZEN Engine" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/hero.png">
## Usage
ZEN Engine is a cross-platform, open-source [Business Rules Engine (BRE)](https://gorules.io) written in **Rust** with native **Python** bindings, alongside Node.js, Go, Java, Kotlin and .NET. Decisions evaluate in microseconds, run identically on every platform, and are stored as portable JSON. Loading the JSON is up to you: file system, database or service call.
ZEN Engine is built as embeddable BRE for your **Rust**, **NodeJS**, **Python** or **Go** applications.
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.
Try it in the free [Online Editor](https://editor.gorules.io) with a built-in simulator, or embed the open-source React [JDM Editor](https://github.com/gorules/jdm-editor) in your own product. Learn more about the [Python rules engine](https://gorules.io/open-source/python-rules-engine) on the GoRules website.
### Installation
## Rules that read like sentences
Conditions are written the way the business says them, in the ZEN Expression Language. The developer view is one toggle away, and the two can never drift apart: there is only one source of truth, and this engine runs it.
<img width="1280" alt="Readable rules" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/tables.png">
## Rules as graphs, or as documents
Model a decision on a visual canvas of decision tables, switches, expressions, functions and reusable sub-decisions. Or write it as a policy document with prose, typed data models and tables. Both compile to the same engine and return the same answers.
<img width="1280" alt="Graphs and documents" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/graphs-docs.png">
To go deeper, see the [Python SDK documentation](https://docs.gorules.io/developers/sdks/python), the [decision graph guide](https://docs.gorules.io/learn/authoring/decision-graphs) and the [ZEN Expression Language](https://docs.gorules.io/learn/zen-language/syntax) reference.
## What's new in 2.0
Version 2.0 is the first stable release of the new engine line:
- **Policy documents**: model decisions as readable documents with typed data models, expressions, decision tables, match blocks and assertions. Policies compile to the same engine as graphs and return the same answers.
- **Workspace analysis**: static type checking across policies and graphs. Type flow, exhaustiveness checking, write-conflict detection and precise diagnostics, all available before anything runs.
- **Per-column collect**: decision table output columns can collect across all matching rows (`tags[]`) while the rest of the table stays first-match.
- **Pre-compiled engine**: decisions are parsed and compiled once at load; evaluation is allocation-light and repeat-safe.
- **Hardened runtime**: out-of-range numbers, arithmetic overflow and malformed inputs return errors or nulls instead of crashing the process.
- **Unified bindings**: configurable loaders, batch evaluation and consistent error envelopes across Node.js, Python, Go and FFI consumers.
## Installation
```bash
pip install zen-engine
```
### Usage
Prebuilt wheels ship for Linux, macOS and Windows; no Rust toolchain required.
To execute a simple decision you can use the code below.
## Quickstart
```python
import zen
# Example filesystem content, it is up to you how you obtain content
with open("./jdm_graph.json", "r") as f:
content = f.read()
content = f.read()
engine = zen.ZenEngine()
@@ -37,7 +60,7 @@ result = decision.evaluate({"input": 15})
### Loaders
For more advanced use cases where you want to load multiple decisions and utilise graphs you can build loaders.
For more advanced use cases where you want to load multiple decisions and reuse them across evaluations you can build loaders. When `engine.evaluate` is invoked it calls the loader with a key, expecting the content of the JDM decision graph in return.
```python
import zen
@@ -50,261 +73,55 @@ engine = zen.ZenEngine({"loader": loader})
result = engine.evaluate("jdm_graph1.json", {"input": 5})
```
When engine.evaluate is invoked it will call loader and pass a key expecting a content of the JDM decision graph.
In the case above we will assume file `jdm_directory/jdm_graph1.json` exists.
The same pattern works for loading from a REST API, S3, a database, or anywhere else. Full guides, including multi-decision graphs and batch evaluation, are in the [Python SDK documentation](https://docs.gorules.io/developers/sdks/python).
Similar to this example you can also utilise loader to load from different places, for example from REST API, from S3, Database, etc.
## Other platforms
### Supported Platforms
* **Node.js** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/nodejs) | [Documentation](https://docs.gorules.io/developers/sdks/nodejs) | [npm](https://www.npmjs.com/package/@gorules/zen-engine)
* **Python** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/python) | [Documentation](https://docs.gorules.io/developers/sdks/python) | [PyPI](https://pypi.org/project/zen-engine/)
* **Go** - [GitHub](https://github.com/gorules/zen-go) | [Documentation](https://docs.gorules.io/developers/sdks/go)
* **Java / Kotlin** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/uniffi) | [Documentation](https://docs.gorules.io/developers/sdks/java) | [Maven Central](https://mvnrepository.com/artifact/io.gorules/zen-engine)
* **.NET** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/uniffi) | [Documentation](https://docs.gorules.io/developers/sdks/csharp) | [NuGet](https://www.nuget.org/packages/GoRules.ZenEngine)
* **Rust (Core)** - [GitHub](https://github.com/gorules/zen) | [Documentation](https://docs.gorules.io/developers/sdks/rust) | [crates.io](https://crates.io/crates/zen-engine)
List of platforms where Zen Engine is natively available:
## The GoRules platform
* **NodeJS** - [GitHub](https://github.com/gorules/zen/blob/master/bindings/nodejs/README.md) | [Documentation](https://gorules.io/docs/developers/bre/engines/nodejs) | [npmjs](https://www.npmjs.com/package/@gorules/zen-engine)
* **Python** - [GitHub](https://github.com/gorules/zen/blob/master/bindings/python/README.md) | [Documentation](https://gorules.io/docs/developers/bre/engines/python) | [pypi](https://pypi.org/project/zen-engine/)
* **Go** - [GitHub](https://github.com/gorules/zen-go) | [Documentation](https://gorules.io/docs/developers/bre/engines/go)
* **Rust (Core)** - [GitHub](https://github.com/gorules/zen) | [Documentation](https://gorules.io/docs/developers/bre/engines/rust) | [crates.io](https://crates.io/crates/zen-engine)
The engine is open at the core; [GoRules](https://gorules.io) is the platform around it. Managed cloud, self-hosted, or embedded with no network hop. SOC 2 Type II.
For a complete **Business Rules Management Systems (BRMS)** solution:
### AI that builds rules, and stays reviewable
* [Self-hosted BRMS](https://gorules.io)
* [GoRules Cloud BRMS](https://gorules.io/signin/verify-email)
An AI copilot and MCP server that edits rules, runs tests and explains decisions. It never deploys. Releases stay with your reviewers.
## JSON Decision Model (JDM)
<img width="800" alt="GoRules AI" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/ai.png">
GoRules JDM (JSON Decision Model) is a modeling framework designed to streamline the representation and implementation of decision models.
### Promote like a release, run like a binary
#### Understanding GoRules JDM
At its core, GoRules JDM revolves around the concept of decision models as interconnected graphs stored in JSON format.
These graphs capture the intricate relationships between various decision points, conditions, and outcomes in a GoRules Zen-Engine.
A release moves from testing to staging to production untouched. Approvals, instant rollback, and a paper trail for every change.
Graphs are made by linking nodes with edges, which act like pathways for moving information from one node to another, usually from the left to the right.
<img width="800" alt="Governance" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/governance.png">
The Input node serves as an entry for all data relevant to the context, while the Output nodes produce the result of decision-making process. The progression of data follows a path from the Input Node to the Output Node, traversing all interconnected nodes in between. As the data flows through this network, it undergoes evaluation at each node, and connections determine where the data is passed along the graph.
### Prove it before it ships
To see JDM Graph in action you can use [Free Online Editor](https://editor.gorules.io) with built in Simulator.
Scenario suites run on every change, coverage is measured against decision paths, and every answer comes with a replayable trace.
There are 5 main node types in addition to a graph Input Node (Request) and Output Node (Response):
* Decision Table Node
* Switch Node
* Function Node
* Expression Node
* Decision Node
### Decision Table Node
#### Overview
Tables provide a structured representation of decision-making processes, allowing developers and business users to express complex rules in a clear and concise manner.
<img width="960" alt="Decision Table" src="https://gorules.io/images/decision-table.png">
#### Structure
At the core of the Decision Table is its schema, defining the structure with inputs and outputs. Inputs encompass business-friendly expressions using the ZEN Expression Language, accommodating a range of conditions such as equality, numeric comparisons, boolean values, date time functions, array functions and more. The schema's outputs dictate the form of results generated by the Decision Table.
Inputs and outputs are expressed through a user-friendly interface, often resembling a spreadsheet. This facilitates easy modification and addition of rules, enabling business users to contribute to decision logic without delving into intricate code.
#### Evaluation Process
Decision Tables are evaluated row by row, from top to bottom, adhering to a specified hit policy.
Single row is evaluated via Inputs columns, from left to right. Each input column represents `AND` operator. If cell is empty that column is evaluated **truthfully**, independently of the value.
If a single cell within a row fails (due to error, or otherwise), the row is skipped.
**HitPolicy**
The hit policy determines the outcome calculation based on matching rules.
The result of the evaluation is:
* **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.
* **`null`/`undefined`** if no rule matched in `first` hit policy
* **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
#### Inputs
In the assessment of rules or rows, input columns embody the `AND` operator. The values typically consist of (qualified) names, such as `customer.country` or `customer.age`.
There are two types of evaluation of inputs, `Unary` and `Expression`.
**Unary Evaluation**
Unary evaluation is usually used when we would like to compare single fields from incoming context separately, for example `customer.country` and `cart.total` . It is activated when a column has `field` defined in its schema.
***Example***
For the input:
```json
{
"customer": {
"country": "US"
},
"cart": {
"total": 1500
}
}
```
<img width="960" alt="Decision Table Unary Test" src="https://gorules.io/images/decision-table.png">
This evaluation translates to
```
IF customer.country == 'US' AND cart.total > 1000 THEN {"fees": {"percent": 2}}
ELSE IF customer.country == 'US' THEN {"fees": {"flat": 30}}
ELSE IF customer.country == 'CA' OR customer.country == 'MX' THEN {"fees": {"flat": 50}}
ELSE {"fees": {"flat": 150}}
```
List shows basic example of the unary tests in the Input Fields:
| Input entry | Input Expression |
| ----------- | ---------------------------------------------- |
| "A" | the field equals "A" |
| "A", "B" | the field is either "A" or "B" |
| 36 | the numeric value equals 36 |
| < 36 | a value less than 36 |
| > 36 | a value greater than 36 |
| [20..39] | a value between 20 and 39 (inclusive) |
| 20,39 | a value either 20 or 39 |
| <20, >39 | a value either less than 20 or greater than 39 |
| true | the boolean value true |
| false | the boolean value false |
| | any value, even null/undefined |
| null | the value null or undefined |
Note: For the full list please visit [ZEN Expression Language](https://gorules.io/docs/rules-engine/expression-language/).
**Expression Evaluation**
Expression evaluation is used when we would like to create more complex evaluation logic inside single cell. It allows us to compare multiple fields from the incoming context inside same cell.
It can be used by providing an empty `Selector (field)` inside column configuration.
***Example***
For the input:
```json
{
"transaction": {
"country": "US",
"createdAt": "2023-11-20T19:00:25Z",
"amount": 10000
}
}
```
<img width="960" alt="Decision Table Expression" src="https://gorules.io/images/decision-table-expression.png">
```
IF time(transaction.createdAt) > time("17:00:00") AND transaction.amount > 1000 THEN {"status": "reject"}
ELSE {"status": "approve"}
```
Note: For the full list please visit [ZEN Expression Language](https://gorules.io/docs/rules-engine/expression-language/).
**Outputs**
Output columns serve as the blueprint for the data that the decision table will generate when the conditions are met during evaluation.
When a row in the decision table satisfies its specified conditions, the output columns determine the nature and structure of the information that will be returned. Each output column represents a distinct field, and the collective set of these fields forms the output or result associated with the validated row. This mechanism allows decision tables to precisely define and control the data output.
***Example***
<img width="860" alt="Decision Table Output" src="https://gorules.io/images/decision-table-output.png">
And the result would be:
```json
{
"flatProperty": "A",
"output": {
"nested": {
"property": "B"
},
"property": 36
}
}
```
### Switch Node (NEW)
The Switch node in GoRules JDM introduces a dynamic branching mechanism to decision models, enabling the graph to diverge based on conditions.
Conditions are written in a Zen Expression Language.
By incorporating the Switch node, decision models become more flexible and context-aware. This capability is particularly valuable in scenarios where diverse decision logic is required based on varying inputs. The Switch node efficiently manages branching within the graph, enhancing the overall complexity and realism of decision models in GoRules JDM, making it a pivotal component for crafting intelligent and adaptive systems.
The Switch node preserves the incoming data without modification; it forwards the entire context to the output branch(es).
<img width="960" alt="Switch / Branching" src="https://gorules.io/images/decision-graph.png">
#### HitPolicy
There are two HitPolicy options for the switch node, `first` and `collect`.
In the context of a first hit policy, the graph branches to the initial matching condition, analogous to the behavior observed in a table. Conversely, under a collect hit policy, the graph extends to all branches where conditions hold true, allowing branching to multiple paths.
Note: If there are multiple edges from the same condition, there is no guaranteed order of execution.
*Available from:*
* Python 0.16.0
* NodeJS 0.13.0
* Rust 0.16.0
* Go 0.1.0
### Functions Node
Function nodes are JavaScript snippets that allow for quick and easy parsing, re-mapping or otherwise modifying the data using JavaScript. Inputs of the node are provided as function's arguments. Functions are executed on top of QuickJS Engine that is bundled into the ZEN Engine.
Function timeout is set to a 50ms.
```js
const handler = (input, {dayjs, Big}) => {
return {
...input,
someField: 'hello'
};
};
```
There are two built in libraries:
* [dayjs](https://www.npmjs.com/package/dayjs) - for Date Manipulation
* [big.js](https://www.npmjs.com/package/big.js) - for arbitrary-precision decimal arithmetic.
### Expression Node
The Expression node serves as a tool for transforming input objects into alternative objects using the Zen Expression Language. When specifying the output properties, each property requires a separate row. These rows are defined by two fields:
- Key - qualified name of the output property
- Value - value expressed through the Zen Expression Language
Note: Any errors within the Expression node will bring the graph to a halt.
<img width="960" alt="Decision Table" src="https://gorules.io/images/expression.png">
### Decision Node
The "Decision" node is designed to extend the capabilities of decision models. Its function is to invoke and reuse other decision models during execution.
By incorporating the "Decision" node, developers can modularize decision logic, promoting reusability and maintainability in complex systems.
<img width="800" alt="Testing" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/tests.png">
## Support matrix
| Arch | Rust | NodeJS | Python | Go |
|:----------------|:-------------------|:-------------------|:-------------------|:-------------------|
| linux-x64-gnu | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| linux-arm64-gnu | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| darwin-x64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| darwin-arm64 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| win32-x64-msvc | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Arch | Python |
|:----------------|:-------------------|
| linux-x64-gnu | :heavy_check_mark: |
| linux-arm64-gnu | :heavy_check_mark: |
| darwin-x64 | :heavy_check_mark: |
| darwin-arm64 | :heavy_check_mark: |
| win32-x64-msvc | :heavy_check_mark: |
We do not support linux-musl currently.
## Contribution
JDM standard is growing and we need to keep tight control over its development and roadmap as there are number of
companies that are using GoRules Zen-Engine and GoRules BRMS.
For this reason we can't accept any code contributions at this moment, apart from help with documentation and additional
tests.
The JDM standard is growing and we need to keep tight control over its development and roadmap, as a number of companies use GoRules ZEN Engine and GoRules BRMS. For this reason we can't accept code contributions at this moment, apart from help with documentation and additional tests.
## License
[MIT License](https://opensource.org/licenses/MIT)
+3 -1
View File
@@ -35,7 +35,9 @@ keywords = ["gorules",
dev = ["black", "bumpver", "isort", "pip-tools", "pytest", "asyncio"]
[project.urls]
Homepage = "https://github.com/gorules/zen"
Homepage = "https://gorules.io"
Documentation = "https://docs.gorules.io/developers/sdks/python"
Repository = "https://github.com/gorules/zen"
[project.scripts]
zenengine = "reader.__main__:main"
+122
View File
@@ -0,0 +1,122 @@
# Java, Kotlin, Android & .NET Rules Engine
**Business logic humans can read and machines can run.** One copy of your rules: the owner reads it, every system runs it.
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Maven Central](https://img.shields.io/maven-central/v/io.gorules/zen-engine.svg)](https://central.sonatype.com/artifact/io.gorules/zen-engine)
[![NuGet](https://img.shields.io/nuget/v/GoRules.ZenEngine.svg)](https://www.nuget.org/packages/GoRules.ZenEngine)
<img width="1280" alt="GoRules ZEN Engine" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/hero.png">
ZEN Engine is a cross-platform, open-source [Business Rules Engine (BRE)](https://gorules.io) written in **Rust**. This directory builds the UniFFI-based bindings for **Java**, **Kotlin**, **Android** and **.NET**, alongside the native Node.js, Python and Go packages. Decisions evaluate in microseconds, run identically on every platform, and are stored as portable JSON. Loading the JSON is up to you: file system, database or service call.
Try it in the free [Online Editor](https://editor.gorules.io) with a built-in simulator, or embed the open-source React [JDM Editor](https://github.com/gorules/jdm-editor) in your own product. Learn more about the [Java rules engine](https://gorules.io/open-source/java-rules-engine), [Kotlin rules engine](https://gorules.io/open-source/kotlin-rules-engine) and [C# rules engine](https://gorules.io/open-source/csharp-rules-engine) on the GoRules website.
## Packages
| Platform | Package | Install | Documentation |
|:---------|:--------|:--------|:--------------|
| Java (JDK 22+) | [`io.gorules:zen-engine`](https://central.sonatype.com/artifact/io.gorules/zen-engine) | `implementation("io.gorules:zen-engine:2.0.0")` | [Java SDK](https://docs.gorules.io/developers/sdks/java) |
| Kotlin | [`io.gorules:zen-engine-kotlin`](https://central.sonatype.com/artifact/io.gorules/zen-engine-kotlin) | `implementation("io.gorules:zen-engine-kotlin:2.0.0")` | [Kotlin SDK](https://docs.gorules.io/developers/sdks/kotlin) |
| Android (AAR) | [`io.gorules:zen-engine-kotlin-android`](https://central.sonatype.com/artifact/io.gorules/zen-engine-kotlin-android) | `implementation("io.gorules:zen-engine-kotlin-android:2.0.0")` | [Android SDK](https://docs.gorules.io/developers/sdks/android) |
| .NET | [`GoRules.ZenEngine`](https://www.nuget.org/packages/GoRules.ZenEngine) | `dotnet add package GoRules.ZenEngine` | [.NET SDK](https://docs.gorules.io/developers/sdks/csharp) |
## Quickstart
### Java
```java
import io.gorules.zen_engine.ZenEngine;
import io.gorules.zen_engine.JsonBuffer;
try (var engine = new ZenEngine(null, null)) {
var ruleJson = Main.class.getResourceAsStream("/rules/pricing.json").readAllBytes();
var decision = engine.createDecision(new JsonBuffer(ruleJson));
var input = new JsonBuffer("""{ "customer": { "tier": "gold" } }""");
var response = decision.evaluate(input, null).join();
System.out.println(response.result());
}
```
### Kotlin
```kotlin
import io.gorules.zen_engine.kotlin.ZenEngine
import io.gorules.zen_engine.kotlin.JsonBuffer
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val ruleJson = object {}.javaClass.getResourceAsStream("/rules/pricing.json")!!.readBytes()
ZenEngine(null, null).use { engine ->
val decision = engine.createDecision(JsonBuffer(ruleJson))
val input = JsonBuffer("""{ "customer": { "tier": "gold" } }""")
val response = decision.evaluate(input, null)
println(response.result)
}
}
```
### .NET
```csharp
using GoRules.ZenEngine;
var engine = new ZenEngine(loader: null, customNode: null);
var decision = engine.CreateDecision(new JsonBuffer(File.ReadAllBytes("my-decision.json")));
var context = new JsonBuffer("""{"input": 42}""");
var response = await decision.Evaluate(context, null);
Console.WriteLine(response.result);
```
Each SDK also supports loader configurations (`Static`, `Filesystem`, `Zip`, `Callback`) that pre-load and pre-compile decisions at engine creation, plus tracing, custom nodes and direct expression evaluation. Full guides are in the per-platform documentation linked above.
## Rules that read like sentences
Conditions are written the way the business says them, in the ZEN Expression Language. The developer view is one toggle away, and the two can never drift apart: there is only one source of truth, and this engine runs it.
<img width="1280" alt="Readable rules" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/tables.png">
## Rules as graphs, or as documents
Model a decision on a visual canvas of decision tables, switches, expressions, functions and reusable sub-decisions. Or write it as a policy document with prose, typed data models and tables. Both compile to the same engine and return the same answers.
<img width="1280" alt="Graphs and documents" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/graphs-docs.png">
To go deeper, see the [decision graph guide](https://docs.gorules.io/learn/authoring/decision-graphs) and the [ZEN Expression Language](https://docs.gorules.io/learn/zen-language/syntax) reference.
## Other platforms
* **Node.js** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/nodejs) | [Documentation](https://docs.gorules.io/developers/sdks/nodejs) | [npm](https://www.npmjs.com/package/@gorules/zen-engine)
* **Python** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/python) | [Documentation](https://docs.gorules.io/developers/sdks/python) | [PyPI](https://pypi.org/project/zen-engine/)
* **Go** - [GitHub](https://github.com/gorules/zen-go) | [Documentation](https://docs.gorules.io/developers/sdks/go)
* **Rust (Core)** - [GitHub](https://github.com/gorules/zen) | [Documentation](https://docs.gorules.io/developers/sdks/rust) | [crates.io](https://crates.io/crates/zen-engine)
## The GoRules platform
The engine is open at the core; [GoRules](https://gorules.io) is the platform around it. Managed cloud, self-hosted, or embedded with no network hop. SOC 2 Type II.
## Support matrix
| Arch | Java / Kotlin | .NET |
|:----------------|:-------------------|:-------------------|
| linux-x64-gnu | :heavy_check_mark: | :heavy_check_mark: |
| linux-arm64-gnu | :heavy_check_mark: | :heavy_check_mark: |
| darwin-x64 | :heavy_check_mark: | :heavy_check_mark: |
| darwin-arm64 | :heavy_check_mark: | :heavy_check_mark: |
| win32-x64-msvc | :heavy_check_mark: | :heavy_check_mark: |
| linux-s390x | :heavy_check_mark: | :x: |
Android (AAR) and iOS (XCFramework) packages are published from the same core via UniFFI.
## Contribution
The JDM standard is growing and we need to keep tight control over its development and roadmap, as a number of companies use GoRules ZEN Engine and GoRules BRMS. For this reason we can't accept code contributions at this moment, apart from help with documentation and additional tests.
## License
[MIT License](https://opensource.org/licenses/MIT)
+28 -6
View File
@@ -166,7 +166,13 @@ publishing {
artifact(tasks["generateJavaSourcesJar"])
artifact(tasks["javadocJarJava"])
configurePom {
configurePom(
pomName = "GoRules ZEN Engine for Java",
pomDescription = "Open-source Business Rules Engine (BRE) for Java, powered by a native Rust core. " +
"Evaluates JSON Decision Models (decision tables, graphs and policies) in microseconds. " +
"Part of the GoRules platform (https://gorules.io). " +
"Documentation: https://docs.gorules.io/developers/sdks/java",
) {
}
}
}
@@ -179,7 +185,13 @@ publishing {
artifact(tasks["generateKotlinSourcesJar"])
artifact(tasks["javadocJarKotlin"])
configurePom {
configurePom(
pomName = "GoRules ZEN Engine for Kotlin",
pomDescription = "Open-source Business Rules Engine (BRE) for Kotlin, powered by a native Rust core. " +
"Evaluates JSON Decision Models (decision tables, graphs and policies) in microseconds. " +
"Part of the GoRules platform (https://gorules.io). " +
"Documentation: https://docs.gorules.io/developers/sdks/kotlin",
) {
dependency("net.java.dev.jna:jna:5.17.0")
}
}
@@ -196,7 +208,13 @@ publishing {
artifact(tasks["generateKotlinAndroidSourcesJar"])
artifact(tasks["javadocJarKotlinAndroid"])
configurePom {
configurePom(
pomName = "GoRules ZEN Engine for Android",
pomDescription = "Open-source Business Rules Engine (BRE) for Android (AAR), powered by a native Rust core. " +
"Evaluates JSON Decision Models (decision tables, graphs and policies) on-device, offline-capable. " +
"Part of the GoRules platform (https://gorules.io). " +
"Documentation: https://docs.gorules.io/developers/sdks/android",
) {
dependency("net.java.dev.jna:jna:5.14.0", "aar")
dependency("androidx.core:core-ktx:1.12.0")
dependency("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
@@ -242,13 +260,17 @@ fun loadCargoVersion(): String {
?: throw GradleException("Version not found in Cargo.toml")
}
fun MavenPublication.configurePom(dependencyConfig: PomDependencyBuilder.() -> Unit) {
fun MavenPublication.configurePom(
pomName: String = "GoRules ZEN Engine",
pomDescription: String = "GoRules ZEN Engine is a cross-platform, Open-Source Business Rules Engine (BRE)",
dependencyConfig: PomDependencyBuilder.() -> Unit,
) {
val depBuilder = PomDependencyBuilder()
depBuilder.dependencyConfig()
pom {
name = "GoRules ZEN Engine"
description = "GoRules ZEN Engine is a cross-platform, Open-Source Business Rules Engine (BRE)"
name = pomName
description = pomDescription
url = "https://gorules.io"
licenses {
@@ -7,7 +7,7 @@
<Company>GoRules</Company>
<Description>ZEN Engine - Business Rules Engine for .NET. Execute JSON Decision Models (JDM) with native performance via Rust FFI bindings.</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/gorules/zen</PackageProjectUrl>
<PackageProjectUrl>https://gorules.io</PackageProjectUrl>
<PackageIcon>icon.png</PackageIcon>
<RepositoryUrl>https://github.com/gorules/zen</RepositoryUrl>
<PackageTags>rules-engine;business-rules;decision-engine;jdm;gorules</PackageTags>
+23 -3
View File
@@ -1,6 +1,12 @@
# GoRules.ZenEngine
# .NET Rules Engine
Open-source Business Rules Engine for .NET. Execute JSON Decision Models (JDM) with native performance powered by Rust.
**Business logic humans can read and machines can run.** One copy of your rules: the owner reads it, every system runs it.
<img width="1280" alt="GoRules ZEN Engine" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/hero.png">
ZEN Engine is a cross-platform, open-source [Business Rules Engine (BRE)](https://gorules.io) written in **Rust** with native **.NET** bindings, alongside Node.js, Python, Go, Java and Kotlin. Decisions evaluate in microseconds, run identically on every platform, and are stored as portable JSON Decision Models (JDM). Loading the JSON is up to you: file system, database or service call.
Try it in the free [Online Editor](https://editor.gorules.io) with a built-in simulator, or embed the open-source React [JDM Editor](https://github.com/gorules/jdm-editor) in your own product. Learn more about the [C# rules engine](https://gorules.io/open-source/csharp-rules-engine) on the GoRules website.
## Installation
@@ -103,8 +109,22 @@ class FileLoader : ZenDecisionLoaderCallback
```
## Other platforms
* **Node.js** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/nodejs) | [Documentation](https://docs.gorules.io/developers/sdks/nodejs) | [npm](https://www.npmjs.com/package/@gorules/zen-engine)
* **Python** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/python) | [Documentation](https://docs.gorules.io/developers/sdks/python) | [PyPI](https://pypi.org/project/zen-engine/)
* **Go** - [GitHub](https://github.com/gorules/zen-go) | [Documentation](https://docs.gorules.io/developers/sdks/go)
* **Java / Kotlin** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/uniffi) | [Documentation](https://docs.gorules.io/developers/sdks/java) | [Maven Central](https://mvnrepository.com/artifact/io.gorules/zen-engine)
* **.NET** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/uniffi) | [Documentation](https://docs.gorules.io/developers/sdks/csharp) | [NuGet](https://www.nuget.org/packages/GoRules.ZenEngine)
* **Rust (Core)** - [GitHub](https://github.com/gorules/zen) | [Documentation](https://docs.gorules.io/developers/sdks/rust) | [crates.io](https://crates.io/crates/zen-engine)
## Links
- [GoRules](https://gorules.io) - the platform around the open-source engine: managed cloud, self-hosted, or embedded. SOC 2 Type II.
- [.NET SDK Documentation](https://docs.gorules.io/developers/sdks/csharp)
- [GitHub Repository](https://github.com/gorules/zen)
- [GoRules Documentation](https://docs.gorules.io/developers/sdks/csharp)
- [JDM Editor](https://editor.gorules.io)
## License
[MIT License](https://opensource.org/licenses/MIT)
+1
View File
@@ -6,6 +6,7 @@ license = "MIT"
version = "2.0.0"
edition = "2021"
repository = "https://github.com/gorules/zen.git"
homepage = "https://gorules.io"
[lib]
doctest = false
+68 -100
View File
@@ -1,136 +1,104 @@
# ZEN Engine
# Rust Rules Engine
ZEN Engine is business friendly Open-Source Business Rules Engine
(BRE) to execute decision models according to the GoRules JSON
Decision Model (JDM) 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.
**Business logic humans can read and machines can run.** One copy of your rules: the owner reads it, every system runs it.
## Resources
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![crates.io](https://img.shields.io/crates/v/zen-engine.svg)](https://crates.io/crates/zen-engine)
[Documentation](https://gorules.io/docs/)
<img width="1280" alt="GoRules ZEN Engine" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/hero.png">
[Online Rules Engine Editor](https://editor.gorules.io/)
ZEN Engine is a cross-platform, open-source [Business Rules Engine (BRE)](https://gorules.io) written in **Rust**. This crate is the core: the same engine that powers the Node.js, Python, Go, Java, Kotlin and .NET bindings, available with zero FFI overhead. Decisions evaluate in microseconds and are stored as portable JSON. Loading the JSON is up to you: file system, database or service call.
Try it in the free [Online Editor](https://editor.gorules.io) with a built-in simulator, or embed the open-source React [JDM Editor](https://github.com/gorules/jdm-editor) in your own product. Learn more about the [Rust rules engine](https://gorules.io/open-source/rust-rules-engine) on the GoRules website.
## Rules that read like sentences
Conditions are written the way the business says them, in the ZEN Expression Language. The developer view is one toggle away, and the two can never drift apart: there is only one source of truth, and this engine runs it.
<img width="1280" alt="Readable rules" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/tables.png">
## Rules as graphs, or as documents
Model a decision on a visual canvas of decision tables, switches, expressions, functions and reusable sub-decisions. Or write it as a policy document with prose, typed data models and tables. Both compile to the same engine and return the same answers.
<img width="1280" alt="Graphs and documents" src="https://raw.githubusercontent.com/gorules/zen/master/.github/images/graphs-docs.png">
To go deeper, see the [Rust SDK documentation](https://docs.gorules.io/developers/sdks/rust), the [decision graph guide](https://docs.gorules.io/learn/authoring/decision-graphs) and the [ZEN Expression Language](https://docs.gorules.io/learn/zen-language/syntax) reference.
## Installation
Add the following to your Cargo.toml file:
```toml
[dependencies]
zen-engine = "0"
zen-engine = "2"
```
## Usage
> **Upgrading from 0.x?** `arbitrary_precision` is no longer a default feature. If you rely on arbitrary-precision number handling, enable it explicitly: `zen-engine = { version = "2", features = ["arbitrary_precision"] }`. Language bindings are unaffected.
To execute a simple decision using a Noop (default) loader you can use the code below.
## Quickstart
```rust
use serde_json::json;
use zen_engine::DecisionEngine;
use zen_engine::model::DecisionContent;
use serde_json::json;
#[tokio::main]
async fn main() {
let decision_content: DecisionContent =
serde_json::from_str(include_str!("./pricing-rules.json")).unwrap();
async fn evaluate() {
let decision_content: DecisionContent = serde_json::from_str(include_str!("jdm_graph.json")).unwrap();
let engine = DecisionEngine::default();
let decision = engine.create_decision(decision_content.into());
let decision = engine.create_decision(decision_content.into()).unwrap();
let result = decision.evaluate(&json!({ "input": 12 })).await;
let response = decision.evaluate(json!({
"customer": { "tier": "gold", "yearsActive": 3 },
"order": { "subtotal": 150, "items": 5 }
}).into()).await.unwrap();
println!("{}", response.result);
// => {"discount":0.15,"freeShipping":true}
}
```
Alternatively, you may create decision indirectly without constructing the engine utilising
`Decision::from` function.
### Loaders
## Loaders
For more advanced use cases where you want to load multiple decisions and utilise graphs you
may use one of the following pre-made loaders:
- FilesystemLoader - with a given path as a root it tries to load a decision based on relative path
- MemoryLoader - works as a HashMap (key-value store)
- ClosureLoader - allows for definition of simple async callback function which takes key as a parameter
and returns an `Arc<DecisionContent>` instance
- NoopLoader - (default) fails to load decision, allows for usage of create_decision
(mostly existing for streamlining API across languages)
### Filesystem loader
Assuming that you have a folder with decision models (.json files) which is located under /app/decisions,
you may use FilesystemLoader in the following way:
Attach a loader to serve decisions by key. Build one declaratively from `LoaderConfig` (`Static`, `Filesystem`, `Zip`), or construct the loader structs in `zen_engine::loader` directly. With a configuration, decisions are pre-loaded and pre-compiled for faster evaluations.
```rust
use serde_json::json;
use zen_engine::DecisionEngine;
use zen_engine::loader::{FilesystemLoader, FilesystemLoaderOptions};
use zen_engine::loader::LoaderConfig;
use serde_json::json;
async fn evaluate() {
let engine = DecisionEngine::new(FilesystemLoader::new(FilesystemLoaderOptions {
root: "/app/decisions"
}));
#[tokio::main]
async fn main() {
let loader = LoaderConfig::Filesystem { path: "./rules".to_string() }
.into_loader()
.unwrap();
let engine = DecisionEngine::default().with_loader(loader);
let context = json!({ "customer": { "joinedAt": "2022-01-01" } });
// If you plan on using it multiple times, you may cache JDM for minor performance gains
// In case of bindings (in other languages, this increase is much greater)
{
let promotion_decision = engine.get_decision("commercial/promotion.json").await.unwrap();
let result = promotion_decision.evaluate(&context).await.unwrap();
}
// Or on demand
{
let result = engine.evaluate("commercial/promotion.json", &context).await.unwrap();
}
let response = engine.evaluate("pricing.json", json!({ "amount": 100 }).into()).await.unwrap();
println!("{}", response.result);
}
```
### Custom loader
Custom backends (REST API, S3, database) implement the `DecisionLoader` trait. Full guides, including all loader variants and expression evaluation, are in the [Rust SDK documentation](https://docs.gorules.io/developers/sdks/rust).
You may create a custom loader for zen engine by implementing `DecisionLoader` trait.
Here's an example of how MemoryLoader has been implemented.
## Other platforms
```rust
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use zen_engine::loader::{DecisionLoader, LoaderError, LoaderResponse};
use zen_engine::model::DecisionContent;
* **Node.js** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/nodejs) | [Documentation](https://docs.gorules.io/developers/sdks/nodejs) | [npm](https://www.npmjs.com/package/@gorules/zen-engine)
* **Python** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/python) | [Documentation](https://docs.gorules.io/developers/sdks/python) | [PyPI](https://pypi.org/project/zen-engine/)
* **Go** - [GitHub](https://github.com/gorules/zen-go) | [Documentation](https://docs.gorules.io/developers/sdks/go)
* **Java / Kotlin** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/uniffi) | [Documentation](https://docs.gorules.io/developers/sdks/java) | [Maven Central](https://central.sonatype.com/artifact/io.gorules/zen-engine)
* **.NET** - [GitHub](https://github.com/gorules/zen/tree/master/bindings/uniffi) | [Documentation](https://docs.gorules.io/developers/sdks/csharp) | [NuGet](https://www.nuget.org/packages/GoRules.ZenEngine)
* **Swift (iOS)** - [GitHub](https://github.com/gorules/zen-ios) | [Documentation](https://docs.gorules.io/developers/sdks/ios)
#[derive(Debug, Default)]
pub struct MemoryLoader {
memory_refs: RwLock<HashMap<String, Arc<DecisionContent>>>,
}
## The GoRules platform
impl MemoryLoader {
pub fn add<K, D>(&self, key: K, content: D)
where
K: Into<String>,
D: Into<DecisionContent>,
{
let mut mref = self.memory_refs.write().unwrap();
mref.insert(key.into(), Arc::new(content.into()));
}
pub fn get<K>(&self, key: K) -> Option<Arc<DecisionContent>>
where
K: AsRef<str>,
{
let mref = self.memory_refs.read().unwrap();
mref.get(key.as_ref()).map(|r| r.clone())
}
pub fn remove<K>(&self, key: K) -> bool
where
K: AsRef<str>,
{
let mut mref = self.memory_refs.write().unwrap();
mref.remove(key.as_ref()).is_some()
}
}
The engine is open at the core; [GoRules](https://gorules.io) is the platform around it. Managed cloud, self-hosted, or embedded with no network hop. SOC 2 Type II.
impl DecisionLoader for MemoryLoader {
fn load<'a>(&'a self, key: &'a str) -> impl Future<Output=LoaderResponse> + 'a {
async move {
self.get(&key)
.ok_or_else(|| LoaderError::NotFound(key.to_string()).into())
}
}
}
```
## Contribution
The JDM standard is growing and we need to keep tight control over its development and roadmap, as a number of companies use GoRules ZEN Engine and GoRules BRMS. For this reason we can't accept code contributions at this moment, apart from help with documentation and additional tests.
## License
[MIT License](https://opensource.org/licenses/MIT)
+1
View File
@@ -6,6 +6,7 @@ license = "MIT"
version = "2.0.0"
edition = "2021"
repository = "https://github.com/gorules/zen.git"
homepage = "https://gorules.io"
[dependencies]
smallvec = { version = "1", features = ["union"] }
+35
View File
@@ -0,0 +1,35 @@
# ZEN Expression Language
**The expression language of [ZEN Engine](https://crates.io/crates/zen-engine)**, the open-source [Business Rules Engine (BRE)](https://gorules.io) from [GoRules](https://gorules.io).
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![crates.io](https://img.shields.io/crates/v/zen-expression.svg)](https://crates.io/crates/zen-expression)
ZEN expressions are readable by business users and fast enough for hot paths: a complete language for conditions, calculations and data transformation, with lexer, parser, compiler, VM, type checking and natural-language rendering included in this crate.
```rust
use serde_json::json;
use zen_expression::evaluate_expression;
fn main() {
let result = evaluate_expression(
"sum(items) * multiplier",
json!({ "items": [10, 20, 30], "multiplier": 2 }).into(),
)
.unwrap();
// => 120
}
```
Reusable expressions compile once via `compile_expression` and evaluate repeatedly against different contexts.
## Resources
- [ZEN Expression Language reference](https://docs.gorules.io/learn/zen-language/syntax) - syntax, [operators](https://docs.gorules.io/learn/zen-language/operators), [built-in functions](https://docs.gorules.io/learn/zen-language/functions) and [date operations](https://docs.gorules.io/learn/zen-language/dates)
- [ZEN Engine](https://github.com/gorules/zen) - the rules engine built on this language
- [GoRules](https://gorules.io) - the platform around the open-source engine
- [Online Editor](https://editor.gorules.io) - try expressions in the free editor with a built-in simulator
## License
[MIT License](https://opensource.org/licenses/MIT)
+2
View File
@@ -4,6 +4,8 @@ description = "Zen Helper Macros"
version = "2.0.0"
edition = "2024"
license = "MIT"
repository = "https://github.com/gorules/zen.git"
homepage = "https://gorules.io"
[lib]
proc-macro = true
+20
View File
@@ -0,0 +1,20 @@
# ZEN Macros
**Helper macros for [ZEN Engine](https://crates.io/crates/zen-engine)**, the open-source [Business Rules Engine (BRE)](https://gorules.io) from [GoRules](https://gorules.io).
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![crates.io](https://img.shields.io/crates/v/zen-macros.svg)](https://crates.io/crates/zen-macros)
Procedural macros used internally by the ZEN Engine crates.
This crate is an internal building block; most users want [zen-engine](https://crates.io/crates/zen-engine) or [zen-expression](https://crates.io/crates/zen-expression).
## Resources
- [ZEN Engine](https://github.com/gorules/zen) - the open-source rules engine
- [Rust SDK documentation](https://docs.gorules.io/developers/sdks/rust)
- [GoRules](https://gorules.io) - the platform around the open-source engine
## License
[MIT License](https://opensource.org/licenses/MIT)
+1
View File
@@ -6,6 +6,7 @@ license = "MIT"
version = "2.0.0"
edition = "2021"
repository = "https://github.com/gorules/zen.git"
homepage = "https://gorules.io"
[dependencies]
zen-expression = { path = "../expression", version = "2.0.0" }
+20
View File
@@ -0,0 +1,20 @@
# ZEN Template Language
**Template rendering for [ZEN Engine](https://crates.io/crates/zen-engine)**, the open-source [Business Rules Engine (BRE)](https://gorules.io) from [GoRules](https://gorules.io).
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![crates.io](https://img.shields.io/crates/v/zen-tmpl.svg)](https://crates.io/crates/zen-tmpl)
Renders `{{ ... }}` interpolations using the [ZEN Expression Language](https://docs.gorules.io/learn/zen-language/syntax), used by the ZEN Engine wherever rule content embeds dynamic values.
This crate is an internal building block; most users want [zen-engine](https://crates.io/crates/zen-engine) or [zen-expression](https://crates.io/crates/zen-expression).
## Resources
- [ZEN Engine](https://github.com/gorules/zen) - the open-source rules engine
- [Rust SDK documentation](https://docs.gorules.io/developers/sdks/rust)
- [GoRules](https://gorules.io) - the platform around the open-source engine
## License
[MIT License](https://opensource.org/licenses/MIT)
+2
View File
@@ -4,6 +4,8 @@ description = "Zen Core Types"
version = "2.0.0"
edition = "2024"
license = "MIT"
repository = "https://github.com/gorules/zen.git"
homepage = "https://gorules.io"
[dependencies]
ahash = { workspace = true }
+20
View File
@@ -0,0 +1,20 @@
# ZEN Types
**Core type system of [ZEN Engine](https://crates.io/crates/zen-engine)**, the open-source [Business Rules Engine (BRE)](https://gorules.io) from [GoRules](https://gorules.io).
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![crates.io](https://img.shields.io/crates/v/zen-types.svg)](https://crates.io/crates/zen-types)
Shared types powering static analysis across the ZEN Engine: type flow, exhaustiveness checking and precise diagnostics for decision graphs, decision tables and policies, available before anything runs.
This crate is an internal building block; most users want [zen-engine](https://crates.io/crates/zen-engine) or [zen-expression](https://crates.io/crates/zen-expression).
## Resources
- [ZEN Engine](https://github.com/gorules/zen) - the open-source rules engine
- [Rust SDK documentation](https://docs.gorules.io/developers/sdks/rust)
- [GoRules](https://gorules.io) - the platform around the open-source engine
## License
[MIT License](https://opensource.org/licenses/MIT)