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.
| Package | Use it for | Extra dependency |
|---|---|---|
@rules-cascade/common | The rule, bundle and manifest types, the cron schedule, VERSION. Nothing here evaluates | none |
@rules-cascade/core | Evaluate, compile, read results. Browser-safe. Re-exports everything in common | decimal.js |
@rules-cascade/core/loader | JSON Schema validation of source documents (Node.js) | ajv 8 |
@rules-cascade/client | createManifestClient: fetch, cache and refresh manifests from a rule server | none |
@rules-cascade/client/react | The useRuleEvaluation hook | react 18 or later |
@rules-cascade/server | The rule server: serves bundles and manifests, evaluates over HTTP | ajv, 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=clientEvaluate
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
| 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.