Kumi
What is Kumi?
Kumi is a declarative DSL for calculation logic — tax rules, pricing, scoring, financial projections — that compiles to plain Ruby and JavaScript.
You declare the shape of your input data and the values you want computed. The compiler determines evaluation order, checks types, detects impossible conditions, and emits dependency-free code for each target.
schema do
input do
array :items do
hash :item do
integer :quantity
decimal :unit_price
end
end
end
value :line_totals, input.items.item.quantity * input.items.item.unit_price
value :subtotal, fn(:sum, line_totals)
end
No loops, no iteration plumbing: line_totals is computed per item because that's where the data lives, and fn(:sum, ...) collapses it back to a scalar. This works through arbitrarily nested arrays.
Why
- One source of truth, two targets. The same schema compiles to Ruby and JavaScript with identical semantics — write pricing logic once, run it in your backend and in the browser preview.
- Broadcasting from data shape. Operations align over arrays automatically based on the declared input structure, including nested and ragged data.
- Static checks at compile time. Type checking, dependency cycle detection, and unsatisfiable-constraint detection happen when the schema is defined, not in production.
- Boring generated code. Output is deterministic, dependency-free, straight-line code with explicit loops. What you read is what runs.
Use Cases
Tax engines, pricing models, financial projections, compliance rules, insurance underwriting, shipping rate calculators — anywhere calculation logic must be correct, auditable, and consistent across platforms.
Status: experimental. Public API may change. Typing and some static checks are still evolving.
Feedback: have a use case or hit a rough edge? Open an issue or reach out (andremuta+kumi@gmail.com).
Install
gem install kumi
Requires Ruby 3.1+. Runtime dependencies: mutex_m and zeitwerk (bundled via Rubygems).
Quick Start
require 'kumi'
module Double
extend Kumi::Schema
schema do
input { integer :x }
value :doubled, input.x * 2
end
end
# Execute in Ruby
result = Double.from(x: 5)
result[:doubled] # => 10
# or just call the method directly
Double._doubled(x: 5) # => 10
# Export to JavaScript (same logic)
Double.write_source("output.mjs", platform: :javascript)
# ./output.mjs
# export function _doubled(input) {
# let t1 = input["x"];
# let t3 = t1 * 2;
# return t3;
# }
You can also override the compilation strategy without touching code by setting
KUMI_COMPILATION_MODE to jit or aot (e.g. export KUMI_COMPILATION_MODE=aot).
Composing Schemas
Schemas can import other schemas — and the compiler inlines everything at compile time. The generated code has no runtime dependency on the imported module; imported logic participates in broadcasting and loop fusion like any locally written expression.
module TaxPolicy
extend Kumi::Schema
schema do
input { decimal :amount }
value :tax, input.amount * 0.15
end
end
module Order
extend Kumi::Schema
schema do
import :tax, from: TaxPolicy
input do
array :items do
hash :item do
decimal :price
end
end
end
# TaxPolicy only knows a scalar :amount — passing a vectorized
# argument applies it per item automatically.
value :item_taxes, fn(:tax, amount: input.items.item.price)
value :total_tax, fn(:sum, item_taxes)
end
end
Order.from(items: [{ price: 100 }, { price: 200 }, { price: 300 }])[:total_tax]
# => 90.0
The generated JavaScript for total_tax shows what "inline everything" means — the imported tax rule and the sum reduction are fused into one loop, with no call to TaxPolicy, no intermediate array, and no runtime to ship:
export function _total_tax(input) {
let t13 = input["items"];
let acc18 = 0.0;
for (let items_i15 = 0; items_i15 < t13.length; items_i15++) {
let items_el14 = t13[items_i15];
let t16 = items_el14["price"];
let t17 = 0.15;
let t12 = t16 * t17;
acc18 += t12;
}
let t6 = acc18;
return t6;
}
See Schema Imports for renamed inputs, multiple imports, and imports over nested arrays.
Performance
Compiled schemas are straight-line code with fused loops — there is no interpreter, rule engine, or library call in the artifact, so the runtime cost is whatever the host language charges for a for loop.
Two playground examples make this tangible:
- Payroll at Scale — a full payroll run (overtime split, progressive withholding bands, ten aggregates) over 10,000 employees in ~0.2 ms per call in desktop Chrome. Press Benchmark ×100 in the Execute tab to measure median / p95 on your own machine.
- Game of Life XL — Conway's rules over an 83,000-cell grid, stepping live in a worker with the nine neighbor reads fused into a single pass — millions of cell updates per second, rendered in real time with a live counter.
Examples
- US Tax Calculator (2024) — a single schema computes federal, state, and FICA taxes across multiple filing statuses. Open in the demo.
- Payroll at Scale — overtime, withholding bands, and aggregates over 10,000 employees with per-run timing. Open in the demo.
- Monte Carlo Portfolio — probabilistic simulations and table visualizations. Open in the demo.
- Conway's Game of Life — array operations powering a grid-based simulation (XL version runs 83k cells live). Open in the demo.
The playground's example picker groups these by theme (language tour, business logic, scale & speed, simulations) — the Language Intro is the best starting point.
Documentation
- Syntax Reference — DSL syntax, types, operators, functions
- Functions Reference — auto-generated docs for all functions and kernels (machine-readable JSON)
- Schema Imports — composing and reusing schemas
- Architecture — the compiler pipeline and IR stack
- Golden Tests — the end-to-end test harness
- Development Guide — tooling, docs generation, IDE integration
License
MIT License. See LICENSE.