Rule Cascade
Usage by language

TypeScript and JavaScript

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

Rule Cascade for TypeScript is four packages in the @rules-cascade scope. They share one version and are released together.

PackageUse it forExtra dependency
@rules-cascade/commonThe rule, bundle and manifest types, the cron schedule, VERSION. Nothing here evaluatesnone
@rules-cascade/coreEvaluate, compile, read results. Browser-safe. Re-exports everything in commondecimal.js
@rules-cascade/core/loaderJSON Schema validation of source documents (Node.js)ajv 8
@rules-cascade/clientcreateManifestClient: fetch, cache and refresh manifests from a rule servernone
@rules-cascade/client/reactThe useRuleEvaluation hookreact 18 or later
@rules-cascade/serverThe rule server: serves bundles and manifests, evaluates over HTTPajv, yaml

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

@rules-cascade/core is not published to npm yet. Until it is, Node.js can evaluate the same bundles through the signed rcas command over the engine protocol, or load rcas.wasm in process under Node's WASI support: download and verify them, then follow Evaluate from another language.

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 '@rules-cascade/core';

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 '@rules-cascade/core';
import { createSchemaValidator } from '@rules-cascade/core/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 '@rules-cascade/client';

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 '@rules-cascade/core';

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 '@rules-cascade/client/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.

On this page