Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
@elite-libs/rules-machine
Advanced tools
Rules Against The Machine 🤘
Table of Content
Rules Machine
?
Rules Machine
?It's a fast, general purpose JSON Rules Engine
library for both the Browser & Node.js! 🚀
applySalesTax
)App Logic != Business Rules
Typically Business Rules are better-suited to 'rules engine' style pattern.
If your Business Rules or logic changes frequently, you can get alignment benefits by moving that logic to a serializable & sharable format. Specifically, this can provide immediate benefits to mobile & native apps, as you don't have to wait for an approvals process for every change. ✨
Typically App Logic & Business Rules are woven together throughout the project. This co-location of logic is usually helpful, keeping things readable in small and even mid-sized projects.
This works great, until you run into one of the following challenges:
DepositTransaction
controller shouldn't require careful reading of 2,000 lines of custom rules around currency hackery & country-code checks.trace
, with details on each step, what happened, and the time taken.yarn add @elite-libs/rules-machine
# Or
npm install @elite-libs/rules-machine
import { ruleFactory } from '@elite-libs/rules-machine';
const fishRhyme = ruleFactory([
{ if: 'fish == "oneFish"', then: 'fish = "twoFish"' },
{ if: 'fish == "redFish"', then: 'fish = "blueFish"' },
]);
// Equivalent to:
// if (fish == "oneFish") fish = "twoFish"
// if (fish == "redFish") fish = "blueFish"
fishyRhyme({ fish: 'oneFish' }); // {fish: 'twoFish'}
// Using "and" object style operator
[
{"if": {"and": ["price >= 25", "price <= 50"]}, "then": "discount = 5"},
{"if": "price > 50", "then": "discount = 10"},
{"return": "discount"}
]
// Using inline AND operator
[
{"if": "price >= 25 AND price <= 50", "then": "discount = 5"},
{"if": "price > 50", "then": "discount = 10"},
{"return": "discount"}
]
- if: { and: [price >= 25, price <= 50] }
then: discount = 5
- if: price > 50
then: discount = 10
- return: discount
[
{
"if": "user.plan == \"premium\"",
"then": "discount = 15"
},
{
"if": "user.employee == true",
"then": "discount = 15"
},
{
"return": "discount"
}
]
[
{
"if": "price <= 100",
"then": "discount = 5"
},
{
"if": {
"or": ["price >= 100", "user.isAdmin == true"]
},
"then": "discount = 20"
},
{
"return": "discount"
}
]
- if: price <= 100
then: discount = 5
- if:
or: [price >= 100, user.isAdmin == true]
then: discount = 20
- return: discount
[
{
"if": "price <= 100",
"then": ["discount = 5", "user.discountApplied = true"]
},
{
"if": {
"and": ["price >= 90", "user.discountApplied != true"]
},
"then": "discount = 20"
},
{
"return": "discount"
}
]
- if: price <= 100
then:
- discount = 5
- user.discountApplied = true
- if:
and:
- price >= 90
- user.discountApplied != true
then: discount = 20
- return: discount
map
const doubleList = ruleFactory([
{
map: 'list',
run: '$item * 2',
set: 'list',
},
]);
doubleList({ list: [1, 2, 3, 4] });
// [2, 4, 6, 8]
filter
const multiplesOfThree = ruleFactory([
{
filter: 'list',
run: '$item % 3 == 0',
set: 'results',
},
{ return: 'results' }
]);
multiplesOfThree({ list: [1, 2, 3, 4] });
// [3]
find
const getFirstMultipleOfThree = ruleFactory([
{
find: 'list',
run: '$item % 3 == 0',
set: 'results',
},
{ return: 'results' }
]);
getFirstMultipleOfThree({list: [1, 2, 3, 4]})
// 3
getFirstMultipleOfThree({list: [9, 3, 4]})
// 9
getFirstMultipleOfThree({list: [99]})
// undefined
every
const isEveryNumberMultipleOfThree = ruleFactory([
{
every: 'list',
run: '$item % 3 == 0',
set: 'results',
},
{ return: 'results' }
]);
isEveryNumberMultipleOfThree({list: [3, 6, 9]})
// true
isEveryNumberMultipleOfThree({list: [3, 6, 9, 10]})
// false
some
const hasEvenNumbers = ruleFactory([
{
some: 'list',
run: '2 % $item == 0',
set: 'results',
},
{ return: 'results' }
]);
hasEvenNumbers({list: [2, 4]})
// true
hasEvenNumbers({list: [2, 4, 5]})
// true
hasEvenNumbers({list: [5]})
// false
if/then
const calculateDiscount = ruleFactory([
{"if": {"and": ["price >= 25", "price <= 50"]}, "then": "discount = 5"},
{"if": "price > 50", "then": "discount = 10"},
{"return": "discount"}
]);
calculateDiscount({price: 40, discount: 0})
// 5
calculateDiscount({price: 60, discount: 0})
// 10
and/or
const isScoreValid = ruleFactory({
"if": {"and": ["score > 0", "score <= 100"]},
"then": "valid = true",
"else": "valid = false",
})
isScoreValid({score: 10})
// { score: 10, valid: true }}
isScoreValid({score: -10})
// { score: 10, valid: false }}
isScoreValid({score: 101})
// { score: 10, valid: false }}
try/catch
Execute string rule from try
. Handle errors in the catch
expression.
[
{
try: 'THROW "error"',
catch: 'status = "Failure"',
},
{ return: 'status' }, // returns "Failure"
]
return
Ends rule execution, returning the specified value.
[
{ return: '"blue"' }, // returns "blue"
{ return: '"green"' }, // is not executed
]
!=
=
- equality check.==
- equality check.<
<=
<>
>
>=
%
- 10 % 2
=> 0
(tip: odd/even check)*
- 42 * 10
=> 420
+
- 42 + 10
=> 52
-
/
^
~=
AND
- this does not short circuit if the first operand is false, but the object form does.OR
- this does not short circuit if the first operand is true, but the object form does.REMOVE_VALUES(matches, input)
- will remove all values matching the item(s) in the 1st argument from the 2nd argument array. (XOR operation.)FILTER_VALUES(matches, input)
- will ONLY INCLUDE values that are in the 1st & 2nd arguments. (Intersection operation.)CONTAINS(42, [41, 42, 43])
=> true
IF(7 > 5, 8, 10)
=> 8
GET('users[2].name', users)
=> Mary
AVERAGE([10, 20, 30])
=> 20
CEIL(0.1)
=> 1
FLOOR(1.9)
=> 1
FLOOR(0.6)
=> 1
TRUNC(1.9)
=> 1
SUM([1,2,3])
=> 6
ADD(2, 3)
=> 5
SUB(2, 3)
=> -1
DIV(9, 3)
=> 3
MUL(3, 3)
=> 9
NEG(ADD(1, 2))
=> -3
NOT(ISPRIME(7))
=> false
ISNAN('hai')
=> true
ISPRIME(7)
=> true
MOD(10, 2)
=> 0
GCD(9, 3)
=> 3
SLICE(1, 3, [1, 42, 69, 54])
=> [42, 69]
LENGTH([42, 69, 54])
=> 3
SORT([2,2,1])
=> [1, 2, 2]
FILTER(isEven, [1,2,3,4,5,6])
=> [2, 4, 6]
INDEX([42, 69, 54], 0)
=> 42
MAP("NOT", [FALSE, TRUE, FALSE])
=> [true, false, true]
MIN([42, 69, 54])
=> 42
MAX([42, 69, 54])
=> 69
HEAD([42, 69, 54])
=> 42
LAST([42, 69, 54])
=> 54
TAIL([42, 69, 54])
=> [69, 54]
TAKE(2, [42, 69, 54])
=> [42, 69]
TAKEWHILE(isEven, [0,2,4,5,6,7,8])
=> [0, 2, 4]
DROP(2, [1, 42, 69, 54])
=> [69, 54]
DROPWHILE(isEven, [0,2,4,5,6,7,8])
=> [5,6,7,8]
REDUCE("ADD", 0, [1, 2, 3])
=> 6
REVERSE([1,2,2])
=> [2, 2, 1]
CHARARRAY("abc")
=> ['a', 'b', 'c']
CONCAT([42, 69], [54])
=> [42, 69, 54]
CONS(2, [3, 4])
=> [2, 3, 4]
JOIN(",", ["a", "b"])
=> a,b
RANGE(0, 5)
=> [0, 1, 2, 3, 4]
UNZIPDICT([["a", 1], ["b", 5]])
=> {a: 1, b: 5}
ZIP([1, 3], [2, 4])
=> [[1, 2], [3, 4]]
DICT(["a", "b"], [1, 4])
=> {a: 1, b: 4}
KEYS(DICT(["a", "b"], [1, 4]))
=> ['a', 'b']
VALUES(DICT(["a", "b"], [1, 4]))
=> [1, 4]
UNZIP([[1, 2], [3, 4]])
=> [[1, 3], [2, 4]]
CONTAINS("x", {x: 1})
=> true
COUNT_KEYS({x: 1})
=> 1
OMIT("x", {x: 1})
=> {}
LOWER('HELLO')
=> hello
UPPER('hello')
=> HELLO
SPLIT(',', 'a,b')
=> ['a', 'b']
CHAR(65)
=> A
CODE('A')
=> 65
BIN2DEC('101010')
=> 42
DEC2BIN(42)
=> 101010
DEC2HEX('42')
=> 2a
DEC2STR('42')
=> 42
HEX2DEC("F")
=> 15
STR2DEC('42')
=> 42
STRING_CONTAINS("lo wo", "hello world")
=> true
, note: this function does not currently accept regular expressionsSTRING_ENDS_WITH("rld", "hello world")
=> true
, note: this function does not currently accept regular expressionsSTRING_STARTS_WITH("hell", "hello world")
=> true
, note: this function does not currently accept regular expressionsSIGN(-42)
=> -1
ABS(-42)
=> 42
THROW("my error") => PARSER FAIL: Error: my error
eval
/Function('...')
parsing.Rule[]
the sole recursive type.{"and": []}
{"or": []}
).{"try": "rules", "catch": {"return": "error"}}
{"run": Rule[] | Rule | "ruleSetName"}
{"log": "rule/value expression"}
{"set": "newVar = value"}
undefined
, valueOf
, toString
, __proto__
, constructor
.FAQs
📐 A fast serializable logical Rules Engine.
The npm package @elite-libs/rules-machine receives a total of 175 weekly downloads. As such, @elite-libs/rules-machine popularity was classified as not popular.
We found that @elite-libs/rules-machine demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.