npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@codeforamerica/blueprint-rules-engine

v0.2.0

Published

Browser-compatible rules evaluator for Blueprint dependency graph rulesets

Readme

@codeforamerica/blueprint-rules-engine

Browser-compatible rules evaluator for Blueprint dependency graph rulesets

npm version license

Pre-release: This package is at 0.x. Until 1.0.0, minor versions may include breaking changes. Pin your version if stability matters.

Installation

npm install @codeforamerica/blueprint-rules-engine

What it does

Evaluates a compiled rule graph — declarative CEL expressions that derive facts from inputs such as household circumstances and policy parameters. It runs in Node.js and in the browser, with no network call and no contract tooling installed.

Compiling a *-rules.yaml contract into a graph is a build step, and it belongs to blueprint-core. This package is the runtime. Keeping the two apart is what lets the evaluator ship to a browser: it depends on a CEL interpreter and nothing else.

Which to use

A ruleset defined in a blueprint contract set is reachable through its API: every ruleset declares an endpoint, POST /{domain}{path}, which the mock server serves and a real implementation provides. An application should call that endpoint — through the typed client blueprint-generate-ts-clients emits, or any HTTP client — so the same call works against the mock, in a page or in Node, and against a real service.

This package is for evaluating a ruleset without the blueprint's APIs: offline or in the browser with no server, in tooling, or in tests of a ruleset itself. It takes a compiled graph, the rules engine's own contract, which needs nothing else from the blueprint. To run a ruleset from the command line, use blueprint-evaluate from @codeforamerica/blueprint-cli.

Usage

import { evaluate } from '@codeforamerica/blueprint-rules-engine';

const nodes = evaluate(graph, {
  household: { members: [{ name: 'Alice', age: 22, employed: true }] },
  policy:    { minAge: 18, maxAge: 65 },
});

nodes.adultsFilter;
// { type: 'output', state: 'complete', value: [ { name: 'Alice', … } ] }

The return is a flat map of every fact in the graph, keyed by fact name. Inputs are keyed by the graph's top-level namespaces — { household: … } for paths beginning $.household.

To produce the graph, compile the contract once at build time:

import { discover, load, generate } from '@codeforamerica/blueprint-core';

const docs   = discover('./contracts', 'rules').map(load);
const graphs = generate(docs, 'graph');   // [{ graph, … }, …]

Compiled graphs are also written to disk as *-graph.yaml by blueprint-resolve, so a consumer can load one directly rather than depending on core at all.

Partial inputs

Inputs may be incomplete. Nothing throws — each fact reports what it could do:

| state | Meaning | |---|---| | complete | Resolved; every input it reads was supplied | | placeholder | Resolved, but at least one input fell back to a default declared in the graph | | missing | Could not compute; missing lists the input paths it could not reach | | error | Could not compute; message says why (bad input type, failed expression, or a dependency in one of those states) |

Each node also carries type, either output (a fact the ruleset declares as an answer) or intermediate (a step on the way to one).

Omit an input that has a declared default and the fact resolves, flagged:

evaluate(graph, { household: { members } });   // no policy supplied

// adultsFilter → { type: 'output', state: 'placeholder', value: [ … ] }
// allEmployed  → { type: 'output', state: 'complete',    value: false }

Defaults are read from the graph's inputs, which declares them per field path, so supplying part of a namespace still defaults the rest of it.

Omit one with no default and its dependents say exactly what they need:

evaluate(graph, { policy: { minAge: 18, maxAge: 65 } });   // no household supplied

// adultsFilter → {
//   type: 'output', state: 'missing', value: null,
//   missing: ['$.household.members[]']
// }

That list is what drives progressive disclosure: ask for the inputs the pending outputs actually need, and nothing else.

TypeScript

Types ship with the package — no @types install.

import { evaluate, type Graph, type FactNode } from '@codeforamerica/blueprint-rules-engine';

const nodes: Record<string, FactNode<unknown>> = evaluate(graph, inputs);

if (nodes.adultsFilter.state === 'missing') {
  nodes.adultsFilter.missing;   // string[] — narrowed by the discriminated union
}

FactNode is a union discriminated on state, so missing and message are reachable only on the states that carry them.

For per-ruleset input and result types rather than unknown, generate a client with blueprint-generate-ts-clients: it emits ${Ruleset}Inputs and ${Ruleset}Result and a Rules export that embeds the graph.

Browser

A pre-built IIFE bundle ships at dist/browser.js, exposed through the ./browser package export. Loaded from a script tag it sets window.RulesEngine:

<script src="https://unpkg.com/@codeforamerica/blueprint-rules-engine/dist/browser.js"></script>
<script>
  const nodes = window.RulesEngine.evaluate(graph, {
    household: { members: [{ name: 'Alice', age: 22, employed: true }] },
  });
</script>

The bundle is an IIFE and exports nothing importable, so the global is the only way to reach it. With a bundler, import evaluate from the package root instead — the global is typed via declare global for the script-tag case.

The bundle is regenerated from source by npm run build:browser. dist/ is not committed — prepare builds it on every install and before publish, and files ships it to npm, so the copy a consumer gets is always built from the src/ beside it. A minified bundle is not byte-identical across platforms, so committing one and checking it had not drifted was a check that could not hold.

Known limitation: decimal precision

CEL has no exact decimal type, so type: number is evaluated in binary floating point — 0.1 + 0.2 is 0.30000000000000004. This only changes an answer on exact-equality comparisons and long accumulation chains, not on ordinary threshold tests.

For exactness today, declare money as type: integer in cents. Tracked in #445.

Conformance

This package is a reference implementation, not a mandated engine. The behaviour any engine must reproduce — the four states, what makes an input missing rather than empty, how a bad input propagates — is written as data in tests/conformance/cases.json: a graph, inputs, and the nodes expected for each. tests/conformance.test.js is one runner over it; an engine in another language needs its own.

CONFORMANCE.md records what happened when engines were run against that corpus — which cases each one satisfies, where they differ, and what an engine could not express at all.

Related packages