feelc
v1.11.4
Published
Portable feelc decision/calculation engine — the real Go engine compiled to WebAssembly. Runs in the browser, Node, bundlers (Vite/webpack/Next) and edge runtimes, with no HTTP API.
Maintainers
Readme
feelc
The portable feelc engine — the real Go decision/calculation engine compiled to WebAssembly,
runnable directly in your TypeScript app with no HTTP API. Results are byte-for-byte identical to
the feelc CLI (same parser, same exact-decimal VM, same determinism).
Runs in the browser, Node 18+, bundlers (Vite / webpack 5 / Next / esbuild), and edge runtimes (Cloudflare Workers, Deno). ESM-only.
npm install feelcQuick start
import { createEngine } from "feelc";
const feelc = await createEngine(); // loads the WASM once
const source = `
model "promo" {}
input cart_total : number >= 0
input is_member : boolean
decision discount_pct : number {
needs: cart_total, is_member
hit: collect max
>= 50 | - => 5
>= 100 | - => 10
- | true => 8
}`;
const { output } = feelc.run(source, "discount_pct", { cart_total: 120, is_member: true });
console.log(output); // 10Three ways to run
The engine fits three workflows; pick per use case.
1. Compile at runtime (one-shot)
Simplest. Ships the .rules text and compiles on each call.
feelc.run(source, "discount_pct", { cart_total: 120, is_member: true });
feelc.verify(source); // { hash, report, blockers }
feelc.model(source); // { name, inputs, decisions }
feelc.required(source, "discount_pct");2. Compile once, evaluate many (reactive / "ultra-opti")
Compile (or load) once, then evaluate repeatedly with no recompilation — ideal for live UIs.
const model = feelc.compile(source);
for (const cart_total of [40, 60, 120]) {
console.log(model.evaluate("discount_pct", { cart_total, is_member: true }).output);
}
model.info(); // model surface for building forms
model.required("discount_pct");
model.dispose(); // free the WASM-side handle when done3. Ship a precompiled artifact (.ir.bin)
Compile ahead of time, ship the binary, and load it in the client (smaller, hides rule source).
The bytes are interchangeable with the native CLI's feelc compile.
// build step (Node) — or: `npx feelc-compile rules.rules -o model.ir.bin`
import { writeFileSync } from "node:fs";
const bytes = feelc.compile(source).export();
writeFileSync("model.ir.bin", bytes);// in the client
const bytes = await (await fetch("/model.ir.bin")).arrayBuffer();
const model = feelc.load(bytes);
model.evaluate("discount_pct", { cart_total: 120, is_member: true });Per-environment notes
Vite / webpack 5 / Next / esbuild — works out of the box; the
.wasmis resolved vianew URL("…", import.meta.url)and emitted as an asset by your bundler. Callingawait createEngine()at the top level needs a modern build target (Vite:build.target: "esnext"), or wrap it in anasyncfunction.Node — the
.wasmis read from the package vianode:fs. Nothing to configure.Edge (Cloudflare Workers / Deno) — import the
.wasmas a module and pass it in:import wasm from "feelc/wasm/feelc.wasm"; // Worker: a WebAssembly.Module const feelc = await createEngine({ wasmBinary: wasm });Any environment can override resolution with
createEngine({ wasmUrl })or{ wasmBinary }(bytes /WebAssembly.Module/ afetchResponse).Multiple instances — one engine per realm by default. Pass
instanceTokenfor isolated instances, or run one engine per Web Worker (the recommended way to scale / offload the main thread).
Errors
Engine failures throw FeelcError. Compile errors carry a structured diag (file/line/col/code):
import { FeelcError } from "feelc";
try {
feelc.run("model x {", "d", {});
} catch (e) {
if (e instanceof FeelcError) console.error(e.message, e.diag);
}Caveats
- Decimal precision. Exact decimals cross back into JS as
number(float64); very large/precise values can lose precision. (The engine itself stays exact.) - Bundle size. The
.wasmis ~6 MB (~1.5 MB gzipped), loaded lazily oncreateEngine().
Releasing (maintainers)
make wasm # build feelc.wasm + wasm_exec.js (from the Go source)
npm ci && npm -w feelc run build && npm -w feelc test
cd packages/engine && npm publish --access public # first release: bootstrap on npmAfter the first publish, enable npm OIDC trusted publishing for feelc; subsequent
releases are published by CI on a v* tag (.github/workflows/npm.yml) with no token.
