Nodora
Javascript SDK

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

  1. 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.
  2. createEvaluator(programJSON) loads the NIR into a fresh evaluator instance. The first call also boots the WebAssembly module; later calls reuse it.
  3. 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.
  4. evaluateAsync("Approve", { ... }) runs the rule. The return value contains the outputs map and an emitted_signals array in the order they were queued.
  5. 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 === 3

See the API reference for the full surface, supported type strings, and the rules around custom functions.

On this page