arboret v0.1.2
Arboret
A descriptive, no-code, plain-text, data-driven query language.
Why use Arboret
Whilst building an application in a highly-restricted Node.js environment, I came into a need to query an evolving dataset. I wanted to be able to specify new queries on-the-fly, and needed the flexibility of performing complex computation when necessary. I couldn't access a database instance, which meant no SQL, and to eval
is to give up the keys to your kingdom. Additionally, I needed the freedom to update the language with new constructs over time, without needing to update anything other than the dependencies. Lastly, I wanted the languaged used to be expressive and friendly to my non-technical peers.
Arboret is a decisions "language" that is written in Human JSON. Since it is data, it can be changed without the need to rebuild or redeploy. It has access to a limited context provided to the interpreter, and can perform manipulations on that data to enable advanced compositions for complex decisions. The design of Arboret satisfies four key goals:
- Descriptive
Everything is expressed in simple English, no symbols - Consistent
With few exceptions, every statement takes the form:-
{
expr: string
params: [ ... ]
}
- Powerful
The language is capable of expressing complex decision making logic - Secure
There's no eval, and no shenanigans — Arboret doesn't do anything you wouldn't
Getting Started
Installation is a doddle:
# or, your package manager of choice
npm install arboret
Once installed, using Arboret is straightforward. You have two options.
If your rules are written in Human JSON, the preferred syntax, you should call parseAndEvaluate
on your unparsed HJson string.
import { Arboret } from 'arboret'
const arboret = new Arboret()
const hjson = `{
expr: number
value: 3
}`
arboret.parseAndEvaluate(hjson) // returns 3
Alternatively, you can write your rules as JSON and parse them separately. If you prefer to do things that way, you should call evaluate
instead.
import { Arboret } from 'arboret'
const arboret = new Arboret()
const json = JSON.parse(`{ "expr": "number", "value": 5 }`)
arboret.evaluate(json) // returns 3
Should your application need to load and execute your ruleset often, pre-caching a parsed copy of your ruleset will improve performance.
Examples
Arithmetic
# (10 + 2) * 5
{
expr: multiply
params:
[
{
expr: add
params:
[
{
expr: number
value: 10
}
{
expr: number
value: 2
}
]
}
{
expr: number
value: 5
}
]
}
A random round of FizzBuzz
# context = { digit: Math.round(Math.random() * 100) }
{
expr: if
params:
[
{
expr: equals
params:
[
{
expr: modulo
params:
[
{
expr: variable
name: digit
}
{
expr: number
value: 15
}
]
}
{
expr: number
value: 15
}
]
}
{
expr: string
value: fizzbuzz
}
{
expr: if
params:
[
{
expr: equals
params:
[
{
expr: modulo
params:
[
{
expr: variable
name: digit
}
{
expr: number
value: 3
}
]
}
{
expr: number
value: 3
}
]
}
{
expr: string
value: fizz
}
{
expr: if
params:
[
{
expr: equals
params:
[
{
expr: modulo
params:
[
{
expr: variable
name: digit
}
{
expr: number
value: 5
}
]
}
{
expr: number
value: 5
}
]
}
{
expr: string
value: buzz
}
{
expr: variable
name: digit
}
]
}
]
}
]
}
Further Reading
See SPECIFICATION for an exhaustive outline of the language.