Quickstart
Compile and evaluate a ruleset from JavaScript
This page walks through compiling a ruleset, creating an evaluator,
listening for emitted signals, and running the rule against an input.
It assumes you have @nodora/js
installed.
A minimal program
import { compile, createEvaluator } from "@nodora/js";
const src = `
signal BlockAccount(user_id)
rule Approve {
is_adult = input.age >= 18
emit BlockAccount(input.user_id) when !is_adult
out approved = is_adult
}
`;
const program = await compile(src);
const evaluator = await createEvaluator(program);
evaluator.on("BlockAccount", (user_id) => {
console.log("blocking", user_id);
});
const result = await evaluator.evaluateAsync("Approve", {
user_id: "u42",
age: 17,
});
console.log(result);
evaluator.destroy();Output:
blocking u42
{
outputs: { approved: false },
emitted_signals: [ { name: 'BlockAccount', args: [ 'u42' ] } ]
}What just happened
compile(src)parses the source, type-checks it, compiles it to NIR, and returns the NIR as a JSON string. You can persist this string anywhere (file, database, blob storage) and skip the compile step next time.createEvaluator(programJSON)loads the NIR into a fresh evaluator instance. The first call also boots the WebAssembly module; later calls reuse it.evaluator.on("BlockAccount", ...)registers a JS callback. When the rule emits the signal, your callback runs with the emission's positional arguments spread in order.evaluateAsync("Approve", { ... })runs the rule. The return value contains theoutputsmap and anemitted_signalsarray in the order they were queued.destroy()releases the evaluator handle on the WASM side. Call it when you're done so handles don't leak.
Loading precompiled NIR
If you compiled with the CLI (nodora compile -f rule.ruleset), skip
the compile call and feed the NIR JSON directly into
createEvaluator:
import { readFile } from "node:fs/promises";
import { createEvaluator } from "@nodora/js";
const program = await readFile("Approve.json", "utf8");
const evaluator = await createEvaluator(program);This is the recommended pattern for production: compile ahead of time, ship the NIR, and only pay the evaluator cost at runtime.
Registering a custom function
You can expose custom code to rulesets by registering a JS function in
a custom namespace. The compiler resolves the function name at compile time
and the evaluator looks it up again at run time, so the registration
must be in place in every process that calls compile or
evaluate for a ruleset that uses it.
If you compile in one place (say, a Node build step) and evaluate in
another (a worker, a browser), you must call registerFunction in
both before invoking the SDK:
import { registerFunction, compile, createEvaluator } from "@nodora/js";
await registerFunction({
namespace: "js",
name: "add",
args: [
{ name: "a", type: "number", required: true },
{ name: "b", type: "number", required: true },
],
returnType: "number",
fn: (a, b) => a + b,
});
const program = await compile(`
rule Sum { out result = js::add(input.x, input.y) }
`);
const evaluator = await createEvaluator(program);
const r = await evaluator.evaluateAsync("Sum", { x: 1, y: 2 });
// r.outputs.result === 3See the API reference for the full surface, supported type strings, and the rules around custom functions.