Rule Cascade
ReferencePackage READMEs

@rules-cascade/client

Fetches, caches and refreshes Rule Cascade manifests from a rule server, and evaluates them in React. It runs in browsers and Node.js; evaluation itself is @rules-cascade/core.

Fetches, caches and refreshes Rule Cascade (README.md) manifests from a rule server, and evaluates them in React. It runs in browsers and Node.js; evaluation itself is @rules-cascade/core.

Entry pointUse it forExtra dependency
@rules-cascade/clientcreateManifestClient, localStorageAdapternone
@rules-cascade/client/reactThe useRuleEvaluation hookreact 18 or later (optional peer)

Fetch a manifest

import { createManifestClient } from '@rules-cascade/client';
import { evaluate } from '@rules-cascade/core';

const manifests = createManifestClient({ baseUrl: '/api/rules' });
const manifest = await manifests.get('acme.payments.transfer');   // cached; revalidated with the ETag
evaluate(manifest, request).decision;

Through an outage of the manifest endpoint

createManifestClient keeps the last manifest it fetched for each ruleset. When the server cannot be reached and a manifest is cached, get returns the cached one instead of throwing, so the form keeps giving feedback; the server still decides when the operation is submitted.

const manifests = createManifestClient({
  baseUrl: '/api/rules',
  onStale: ({ rulesetId, error, fetchedAt }) => console.warn(`${rulesetId}: using the rules fetched at ${new Date(fetchedAt).toISOString()}: ${error.message}`),
});
const manifest = await manifests.get('acme.payments.transfer');  // fresh, revalidated (304), or the cached one during an outage
manifests.stale('acme.payments.transfer');                       // undefined, or { rulesetId, manifest, error, fetchedAt } when the last get fell back
  • "Cannot be reached" means: the request failed, or the answer was 408, 429, a 5xx status, or not a client manifest (for example the error page of a proxy).
  • get throws when nothing is cached for that ruleset. The cache lives in memory, for the lifetime of the client object: it does not survive a page reload.
  • Any other 4xx answer is a refusal by the server (the ruleset is gone, or the caller may not read it). get throws and drops the cached manifest, so a later outage does not bring it back.
  • onStale is called on every get that falls back. stale(rulesetId) describes the last get for that ruleset and is undefined once a later get succeeds.

Caching manifests

createManifestClient revalidates on every get by default. Two other modes trade freshness for fewer requests, and every mode can keep its cache in localStorage and refresh in the background:

import { createManifestClient, localStorageAdapter } from '@rules-cascade/client';

const manifests = createManifestClient({
  baseUrl: '/api/rules',
  mode: 'ttl',                     // 'revalidate' (default) | 'ttl' | 'permanent'
  ttlMs: 5 * 60_000,               // no request while the cached copy is younger; refreshed in the background
  refreshCron: '*/15 * * * *',     // optional, any mode
  storage: localStorageAdapter(),  // survives a page reload; does nothing where storage is unavailable
  onUpdate: (rulesetId, manifest) => console.info(`${rulesetId} is now ${manifest.checksum}`),
});
await manifests.get('acme.payments.transfer', { checksum });   // permanent: fetch by checksum URL
manifests.close();                                               // stop the timers

Concurrent get calls for one ruleset share a request. staleIfError: false turns off the fallback to the cached copy. The modes, the server's matching cache headers and CDN guidance are in docs/caching.md.

In React

import { useRuleEvaluation } from '@rules-cascade/client/react';

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

A third argument takes the custom operators the manifest needs. Pass the same object on every render, for example a module-level constant. examples/frontend-react is a complete form built this way.

On this page