Rule Cascade
Usage by language

TypeScript and JavaScript

@yarlisaisolutions/rule-cascade: evaluate in the browser, in Node.js and in React; compile rulesets in Node.js.

The package @yarlisaisolutions/rule-cascade evaluates manifests and bundles in any JavaScript runtime and compiles source rulesets in Node.js. Its only runtime dependency is decimal.js.

Entry pointUse it forExtra dependency
@yarlisaisolutions/rule-cascadeEvaluate, compile, read results, createManifestClient. Browser-safenone
@yarlisaisolutions/rule-cascade/reactThe useRuleEvaluation hookreact 18 or later
@yarlisaisolutions/rule-cascade/loaderJSON Schema validation of source documents (Node.js)ajv 8

Supported versions: Node.js 20 or later (engines in package.json). CI tests Node.js 20 on Linux and 22 on Linux, Windows and macOS. No maximum is declared.

Install from the repository

git clone https://github.com/YarlisAISolutions/rule-cascade.git
cd rule-cascade && npm ci && npm run build      # builds packages/typescript/dist
cd /path/to/your-app
npm install /path/to/rule-cascade/packages/typescript
npm install ajv                                  # only to compile source rulesets in Node.js

For a reproducible artefact, run npm pack in packages/typescript and install the .tgz it writes.

Load

Read a bundle compiled in CI. This needs no YAML parser and runs no load-time check: the compiler already did.

import { readFileSync } from 'node:fs';
import { RuleSet } from '@yarlisaisolutions/rule-cascade';

const rules = RuleSet.fromBundle(JSON.parse(readFileSync('acme.payments.transfer.bundle.json', 'utf8')));
rules.checksum;   // 'sha256:c192dd53b5b1d307d52ccbc27fc1674114e8714d53b699b24088a648ae242c7e'

Or compile source documents at start-up (Node.js):

import { readFileSync } from 'node:fs';
import { parse } from 'yaml';
import { loadRuleSet } from '@yarlisaisolutions/rule-cascade';
import { createSchemaValidator } from '@yarlisaisolutions/rule-cascade/loader';

const read = (file: string) => parse(readFileSync(`contracts/${file}`, 'utf8'));
const registry = {
  'acme.org.base': read('acme-org-base.ruleset.yaml'),
  'acme.payments.transfer': read('payments-transfer.ruleset.yaml'),
};
const rules = loadRuleSet(registry['acme.payments.transfer'], registry, {
  validateSchema: createSchemaValidator(),
  schemaLoader: (file) => read(file.replace(/^\.\//, '')),
});

Without validateSchema the document is not validated against the JSON Schema; without schemaLoader unknown paths (PATH_UNKNOWN) are not detected.

In a browser, fetch the client manifest instead. It is cached and revalidated with its ETag:

import { createManifestClient } from '@yarlisaisolutions/rule-cascade';

const manifests = createManifestClient({ baseUrl: '/api/rules' });
const manifest = await manifests.get('acme.payments.transfer');   // GET /api/rules/rulesets/acme.payments.transfer/manifest?channel=client

Evaluate

import { computedValues, evaluate, fieldStates, findingsFor } from '@yarlisaisolutions/rule-cascade';

const request = {
  entity: 'Transfer',
  operation: 'create',
  data: { type: 'international', amount: 12000, currency: 'USD', beneficiary: { name: 'Ana', country: 'ES' } },
  actor: { id: 'u-1', roles: ['teller'] },
};

// Backend: the server channel of a bundle.
const result = rules.evaluate(request, 'server', operators);
result.decision;                              // 'deny'
result.findings.map((f) => f.code);           // ['ORG-TRF-003', 'PAY-TRF-002', 'PAY-TRF-003']
computedValues(result);                       // { '/fee': 180 }

// Browser: a client manifest.
const advice = evaluate(manifest, request, operators);
fieldStates(advice)['/beneficiary/swiftCode'];        // { visible: true, required: true }
findingsFor(advice, '/beneficiary/swiftCode');        // [{ code: 'PAY-TRF-002', ... }]

In React, the hook evaluates on every render:

import { useRuleEvaluation } from '@yarlisaisolutions/rule-cascade/react';

const { allowed, states, computed, findingsFor } = useRuleEvaluation(manifest, {
  entity: 'Transfer', operation: 'create', data: form, actor, resolutions,
});

operators is optional: the custom operators (x-*) the ruleset declares, as plain functions. Pass the same object on every render.

Errors

WhatHow it surfacesWhat to do
A bundle that is not format 1.x, or has no usable manifestsLoadError from RuleSet.fromBundle, .codes is ['BUNDLE_UNSUPPORTED'] or ['BUNDLE_INVALID']Stop start-up
A source ruleset that fails a checkLoadError from loadRuleSet; .problems lists { code, message, rule? }Stop start-up; fix it in CI
A request of the wrong shapeRequestError, for example 'data' must be an object; requestProblem(request) returns the same text without throwingAnswer 400
A rule that cannot be evaluatedNo exception: a blocking finding with code RULE-EVALUATION-ERROR and a detailAlert on it
A custom operator the host did not registerThe same RULE-EVALUATION-ERROR finding (custom operator x-luhn is not registered)Check missingOperators(rules.manifest('server'), operators) at start-up

More

The full API, including the engine protocol (rule-cascade-node engine), expressions and number handling, is in the package README. Complete applications: React form and the Node.js listing in API endpoint.

Source: site/content/docs/usage/typescript.mdx

On this page