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 point | Use it for | Extra dependency |
|---|---|---|
@yarlisaisolutions/rule-cascade | Evaluate, compile, read results, createManifestClient. Browser-safe | none |
@yarlisaisolutions/rule-cascade/react | The useRuleEvaluation hook | react 18 or later |
@yarlisaisolutions/rule-cascade/loader | JSON 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.jsFor 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=clientEvaluate
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
| What | How it surfaces | What to do |
|---|---|---|
| A bundle that is not format 1.x, or has no usable manifests | LoadError from RuleSet.fromBundle, .codes is ['BUNDLE_UNSUPPORTED'] or ['BUNDLE_INVALID'] | Stop start-up |
| A source ruleset that fails a check | LoadError from loadRuleSet; .problems lists { code, message, rule? } | Stop start-up; fix it in CI |
| A request of the wrong shape | RequestError, for example 'data' must be an object; requestProblem(request) returns the same text without throwing | Answer 400 |
| A rule that cannot be evaluated | No exception: a blocking finding with code RULE-EVALUATION-ERROR and a detail | Alert on it |
| A custom operator the host did not register | The 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.