devlish-runtime

Run business rules inside your app.

One npm package. No backend. No API calls. Your rules ship as a static file and execute right where your code runs: in the browser, on the server, at the edge.

The 60-second version

  1. Write a rule in Devlish (structured English)
    Ask "Customer tier?" as customer_tier
    Ask "Deal size?" as deal_size
    
    discount_rate equals 0.05
    If customer_tier is "enterprise"
      discount_rate equals 0.15
    
    discount equals deal_size times discount_rate
    Respond with discount
  2. Compile it (turns English into a small JSON file)
    devlish compile pricing.dvl --output pricing.dvlc.json
  3. Run it in your app
    import { loadTool } from "devlish-runtime";
    
    const tool = await loadTool({
      bytecode: await fetch("/rules/pricing.dvlc.json").then(r => r.json())
    });
    
    const result = await tool.run({
      customer_tier: "enterprise",
      deal_size: 500000
    });
    
    console.log(result.response); // 75000

That compiled JSON file is the only artifact. Drop it in your /public folder, fetch it at runtime, and call tool.run() with your data. The result comes back as plain JSON.

Works everywhere JavaScript runs

React / Next.js Import in a component, call in useEffect or a server action.
Vue / Nuxt Works in setup(), composables, or server middleware.
Svelte / SvelteKit Call from load functions or client-side scripts.
Plain TypeScript Full type definitions included. Zero config.
Node.js scripts Use in CLI tools, cron jobs, or backend services.
Edge (Cloudflare Workers, Vercel Edge) Runs anywhere with WebAssembly support.

How it actually works (the Rust and WebAssembly part)

If you have heard of WebAssembly (WASM) but are not sure what it is: it is a way to run code written in languages like Rust or C++ inside a web browser (or Node.js) at near-native speed. Think of it as a tiny, fast virtual machine that lives inside your JavaScript runtime.

1
Your .dvl file

Human-readable English. This is what your team writes and reviews.

2
Devlish compiler (Rust)

Parses the English and produces a compact bytecode JSON file. Runs on your machine at build time.

3
Bytecode JSON

A small, portable file (typically 2-20 KB). Ship it like any static asset.

4
devlish-runtime (WASM VM)

A Rust virtual machine compiled to WebAssembly and bundled inside the npm package. When you call loadTool(), it boots a tiny isolated VM, feeds it your bytecode, and returns the result. The VM is built in Rust for speed and memory safety; WebAssembly makes it portable to any platform.

Why Rust and WebAssembly?

Security model

The runtime enforces three layers of protection:

Permission check If a rule was written to access the network or filesystem, the runtime rejects it before it runs. Rules that only compute on data you pass in are allowed.
Instruction limit Every rule has a maximum number of operations (default: 10 million). If a rule has a bug that causes an infinite loop, the runtime stops it and returns an error instead of freezing your app.
Memory isolation Each rule runs in its own WebAssembly memory. It cannot read or write anything in your JavaScript application.

What "off the main thread" means

By default, loadTool() runs the VM inside a Web Worker. That means rule execution happens in a background thread. Your React components keep rendering, your animations keep playing, and your click handlers keep responding while the rule is running. If you prefer synchronous execution (for server-side code where there is no UI to block), pass mainThread: true.

Full API reference

loadTool(options)

Load a compiled rule for repeated execution. Returns a tool you can call multiple times.

const tool = await loadTool({
  bytecode: myCompiledRule,     // required: the compiled JSON (object or string)
  mainThread: false,            // optional: skip Web Worker, run synchronously
  instructionLimit: 10_000_000  // optional: max operations before timeout
});

const result = await tool.run({ key: "value" });
tool.dispose(); // clean up when done

runTool(bytecode, input)

One-shot convenience. Loads, runs, and cleans up in one call. Good for infrequent use.

import { runTool } from "devlish-runtime";

const result = await runTool(myCompiledRule, { customer_tier: "premium" });

Result shape

{
  success: true,         // did the rule complete without errors?
  responded: true,       // did it produce a response value?
  response: 75000,       // the value from "Respond with ..."
  context: { ... },      // all variables after execution
  results: { ... }       // exported data and events
}

Error handling

const result = await tool.run(input);

if (!result.success) {
  console.error(result.error);
  // "Instruction limit exceeded (10000000 instructions)"
  // "Tool requires permissions unavailable in WASM: http_request"
  // "Validation failed: amount must be at least 1000"
}

Example: pricing calculator in React

import { useState, useEffect } from "react";
import { loadTool } from "devlish-runtime";

function PricingWidget({ tier, dealSize }) {
  const [discount, setDiscount] = useState(null);
  const [tool, setTool] = useState(null);

  useEffect(() => {
    loadTool({
      bytecode: fetch("/rules/pricing.dvlc.json").then(r => r.json())
    }).then(setTool);
    return () => tool?.dispose();
  }, []);

  useEffect(() => {
    if (!tool) return;
    tool.run({ customer_tier: tier, deal_size: dealSize })
      .then(result => {
        if (result.success) setDiscount(result.response);
      });
  }, [tool, tier, dealSize]);

  if (discount === null) return <span>Calculating...</span>;
  return <span>Discount: ${discount.toLocaleString()}</span>;
}

Ready to try it?

npm install devlish-runtime

Write a rule in the playground View source on GitHub