Skip to main content
Install the ZEN Engine and evaluate your first decision in Node.js.

Installation

npm install @gorules/zen-engine

Basic usage

import { ZenEngine } from '@gorules/zen-engine';
import fs from 'fs';

const content = fs.readFileSync('./pricing-rules.json');

const engine = new ZenEngine();
const decision = engine.createDecision(content);

const response = await decision.evaluate({
  customer: { tier: 'gold', yearsActive: 3 },
  order: { subtotal: 150, items: 5 }
});

console.log(response.result);
// => { discount: 0.15, freeShipping: true }

engine.dispose();

Loader

The loader pattern enables dynamic decision loading from any storage backend. Combined with ZenDecisionContent for pre-compilation, this provides optimal performance for multi-decision applications.

File system

import { ZenEngine, ZenDecisionContent } from '@gorules/zen-engine';
import fs from 'fs/promises';
import path from 'path';

const cache = new Map();

const engine = new ZenEngine({
  loader: async (key) => {
    if (cache.has(key)) {
      return cache.get(key);
    }

    const buffer = await fs.readFile(path.join('./rules', key));
    const content = new ZenDecisionContent(buffer);
    cache.set(key, content);
    return content;
  }
});

const response = await engine.evaluate('pricing.json', { amount: 100 });
console.log(response.result);

AWS S3

import { ZenEngine, ZenDecisionContent } from '@gorules/zen-engine';
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import AdmZip from 'adm-zip';

const s3 = new S3Client({ region: 'us-east-1' });
const rules = new Map();

// Download and extract all decisions at startup
const command = new GetObjectCommand({
  Bucket: 'my-rules-bucket',
  Key: 'decisions.zip'
});

const s3Response = await s3.send(command);
const zipBuffer = Buffer.from(await s3Response.Body.transformToByteArray());
const zip = new AdmZip(zipBuffer);

for (const entry of zip.getEntries()) {
  if (!entry.isDirectory) {
    rules.set(entry.entryName, new ZenDecisionContent(entry.getData()));
  }
}

const engine = new ZenEngine({
  loader: async (key) => rules.get(key)
});

const response = await engine.evaluate('pricing.json', { amount: 100 });

Azure Blob Storage

import { ZenEngine, ZenDecisionContent } from '@gorules/zen-engine';
import { BlobServiceClient } from '@azure/storage-blob';
import AdmZip from 'adm-zip';

const blobService = BlobServiceClient.fromConnectionString(process.env.AZURE_STORAGE_CONNECTION);
const container = blobService.getContainerClient('rules');
const rules = new Map();

// Download and extract all decisions at startup
const blob = container.getBlobClient('decisions.zip');
const zipBuffer = await blob.downloadToBuffer();
const zip = new AdmZip(zipBuffer);

for (const entry of zip.getEntries()) {
  if (!entry.isDirectory) {
    rules.set(entry.entryName, new ZenDecisionContent(entry.getData()));
  }
}

const engine = new ZenEngine({
  loader: async (key) => rules.get(key)
});

const response = await engine.evaluate('pricing.json', { amount: 100 });

Google Cloud Storage

import { ZenEngine, ZenDecisionContent } from '@gorules/zen-engine';
import { Storage } from '@google-cloud/storage';
import AdmZip from 'adm-zip';

const storage = new Storage();
const bucket = storage.bucket('my-rules-bucket');
const rules = new Map();

// Download and extract all decisions at startup
const [zipBuffer] = await bucket.file('decisions.zip').download();
const zip = new AdmZip(zipBuffer);

for (const entry of zip.getEntries()) {
  if (!entry.isDirectory) {
    rules.set(entry.entryName, new ZenDecisionContent(entry.getData()));
  }
}

const engine = new ZenEngine({
  loader: async (key) => rules.get(key)
});

const response = await engine.evaluate('pricing.json', { amount: 100 });

Error handling

Using try-catch:
try {
  const response = await decision.evaluate(input);
  console.log(response.result);
} catch (error) {
  console.error('Evaluation failed:', error.message);
}
Using safeEvaluate:
const response = await decision.safeEvaluate(input);

if (response.success) {
  console.log(response.data.result);
} else {
  console.error('Evaluation failed:', response.error);
}

Tracing

Enable tracing to inspect decision execution:
const response = await decision.evaluate(input, { trace: true });

console.log(response.trace);
// Each node's input, output, and performance timing

console.log(response.performance);
// Total evaluation time

Expression utilities

Evaluate ZEN expressions outside of a decision context:
import {
  evaluateExpression,
  evaluateUnaryExpression
} from '@gorules/zen-engine';

// Standard expressions
const sum = await evaluateExpression('a + b', { a: 5, b: 3 });
// => 8

const total = await evaluateExpression('sum(items)', { items: [1, 2, 3, 4] });
// => 10

// Unary expressions (comparison against $)
const isValid = await evaluateUnaryExpression('>= 5', { $: 10 });
// => true

const inList = await evaluateUnaryExpression('"US", "CA", "MX"', { $: 'US' });
// => true
Synchronous versions are also available:
import {
  evaluateExpressionSync,
  evaluateUnaryExpressionSync
} from '@gorules/zen-engine';

const result = evaluateExpressionSync('a * 2', { a: 10 });
// => 20

Best practices

Use ZenDecisionContent for caching. Pre-compiling decisions avoids repeated parsing overhead. Cache compiled content in a Map keyed by decision name. Initialize the engine once. Create a single ZenEngine instance at application startup and reuse it for all evaluations. Implement a loader for dynamic decisions. The loader pattern centralizes decision loading logic and enables caching at the source. Call dispose() on shutdown. Release engine resources when your application terminates to prevent memory leaks.