Nodora
Javascript SDK

API reference

Public API of @nodora/js

The package exports four named entries: compile, createEvaluator, registerFunction, and the Evaluator class.

import {
  compile,
  createEvaluator,
  registerFunction,
  Evaluator,
} from "@nodora/js";

compile(src)

compile(src: string): Promise<string>

Compiles a ruleset source string and returns the NIR program as a JSON string. Rejects with an Error if parsing or semantic analysis fails; the message preserves the position reported by the compiler.

const nir = await compile("rule R { out ok = true }");

createEvaluator(programJSON)

createEvaluator(programJSON: string): Promise<Evaluator>

Loads a compiled NIR JSON string and returns an Evaluator bound to it. The first call also initializes the WebAssembly module; subsequent calls reuse it.

Pass either the output of compile or NIR JSON produced ahead of time by the CLI.

Evaluator

interface Evaluator {
  getId(): number;
  evaluate(ruleName: string, input?: Record<string, any>): EvaluationResult;
  evaluateAsync(ruleName: string, input?: Record<string, any>): Promise<EvaluationResult>;
  on(signalName: string, callback: (...args: any[]) => void): Evaluator;
  destroy(): void;
}

type EvaluationResult = {
  outputs: Record<string, any>;
  emitted_signals: { name: string; args: any[] }[];
};

evaluate(ruleName, input?)

Runs the named rule against input and returns the result synchronously. Throws if ruleName is not declared in the program.

evaluateAsync(ruleName, input?)

Same semantics as evaluate, returned as a Promise. Both forms run the rule to completion on the calling stack; the async variant exists purely for ergonomics when you want to mix it with other awaited work.

on(signalName, callback)

Registers a callback for emissions of a given signal. The signal's positional arguments are spread into the callback in declaration order. Returns the evaluator for chaining.

evaluator
    .on("BlockAccount", (user_id) => { /* ... */ })
    .on("Audit",        (user_id) => { /* ... */ });

If a callback throws, the error is swallowed and the evaluation continues. This is to keep rule output stable in the face of buggy listeners.

getId()

Returns the integer handle the WASM runtime uses to track this evaluator. Useful only for debugging.

destroy()

Releases the evaluator handle on the WASM side. Call it when you're done. After destroy, all other methods throw.

registerFunction(options)

registerFunction(options: {
  name: string;
  namespace?: string;
  args?: { name: string; type: Type; required?: boolean }[];
  returnType: Type;
  fn: (...args: any[]) => any;
}): Promise<void>

Adds a function to the runtime registry so rulesets can call it.

The registration is consulted at both compile time and evaluation time:

  • The compiler resolves the function's namespace and name when it sees the call expression, so registerFunction must run before compile.
  • The evaluator looks the function up again on every call, so the same registration must also exist in any process that calls evaluate or evaluateAsync for a ruleset that uses it.

If you compile in one process and evaluate in another (for example, build-time compile in Node and run-time evaluate in a browser), call registerFunction in both. NIR does not embed the function body, only its qualified name; the host has to provide the implementation.

await registerFunction({
    namespace: "js",
    name: "is_corporate_email",
    args: [{ name: "email", type: "string", required: true }],
    returnType: "bool",
    fn: (email) => /@(corp|company)\./.test(email),
});

Type strings

The type and returnType fields accept these strings:

FormMeaning
"string"String value.
"number"Numeric value (stored as float64 internally).
"bool"Boolean.
"object"Object (string-keyed map).
"any"Top type, accepts anything.
"array<T>"Array of T. T may be any of the other forms.
"array"Shorthand for array<any>.
"A|B|..."Union of two or more of the above.

Rules

  • The function must be synchronous. Returning a Promise makes the evaluation that calls it reject with async functions are not supported.
  • If fn throws, the evaluator surfaces the error to the caller of evaluate / evaluateAsync with a message containing the function's fully qualified name.
  • A name may only be registered once per namespace. Re-registering the same name rejects.
  • Omitting namespace registers the function under the core namespace, callable by bare name from rulesets.

Lifecycle notes

  • The WebAssembly module is loaded lazily on the first compile or createEvaluator call and cached for the lifetime of the process.
  • The Node and browser entry points are wired through the package's conditional exports, so bundlers and Node both pick the right loader without any configuration on your part.
  • Each Evaluator keeps its own registry of signal listeners. Listeners registered on one evaluator do not fire for emissions from another.

On this page