json-logic-js business rules: working versions (tested 2026-10-16)
Evaluate business rules with json-logic-js over data objects using and, or, comparisons, var defaults, in and if, plus a custom operation registered with add_operation; assert each result offline.
passed6 passed · 0 failed2026-10-16 20:33 UTC- Status
- passed
- Last passed
- 2026-10-16 20:33 UTC
- Tests
- 6 / 6
- Language
- TypeScript
- Runtime
- node v22.23.3
pinned versions3
json-logic-js@2.0.5@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false npm test
test environment
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38962988948
The code
Exactly the files that passed. Your agent can pull them with get_recipe("json-logic-js-rules") (install), or see them on GitHub.
package.json
{
"name": "json-logic-js-rules",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"json-logic-js": "2.0.5"
},
"devDependencies": {
"@types/node": "26.6.5",
"tsx": "4.23.15"
}
}
src/rules.ts
import jsonLogic from "json-logic-js";
jsonLogic.add_operation("volume_discount", (quantity: number, unitPrice: number) => {
const subtotal = quantity * unitPrice;
if (quantity >= 100) return (subtotal * 4) / 5;
if (quantity >= 10) return (subtotal * 9) / 10;
return subtotal;
});
export const rules = {
freeShipping: {
and: [
{ ">=": [{ var: "total" }, 100] },
{ "==": [{ var: "country" }, "US"] },
],
},
needsReview: {
or: [
{ ">=": [{ var: "total" }, 1000] },
{ in: [{ var: "country" }, ["IR", "KP"]] },
],
},
customerAge: { var: ["customer.age", 0] },
isGiftOrder: { in: ["gift", { var: ["items", []] }] },
shipping: {
if: [
{ var: ["isDigital", false] },
"digital-delivery",
{ ">=": [{ var: "total" }, 100] },
"free-shipping",
"paid-shipping",
],
},
};
export const volumeDiscountRule = {
volume_discount: [{ var: "quantity" }, { var: "unitPrice" }],
};
export function evaluate(rule: unknown, data: Record<string, unknown>): unknown {
return jsonLogic.apply(rule, data);
}
test/rules.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import jsonLogic from "json-logic-js";
import { rules, volumeDiscountRule, evaluate } from "../src/rules.ts";
test("and combines comparison predicates", () => {
assert.equal(jsonLogic.apply(rules.freeShipping, { total: 150, country: "US" }), true);
assert.equal(jsonLogic.apply(rules.freeShipping, { total: 150, country: "CA" }), false);
assert.equal(jsonLogic.apply(rules.freeShipping, { total: 50, country: "US" }), false);
});
test("or combines predicates and in matches array values", () => {
assert.equal(jsonLogic.apply(rules.needsReview, { total: 1500, country: "US" }), true);
assert.equal(jsonLogic.apply(rules.needsReview, { total: 20, country: "KP" }), true);
assert.equal(jsonLogic.apply(rules.needsReview, { total: 20, country: "US" }), false);
});
test("var reads nested paths and falls back to a default", () => {
assert.equal(jsonLogic.apply(rules.customerAge, { customer: { age: 30 } }), 30);
assert.equal(jsonLogic.apply(rules.customerAge, { customer: {} }), 0);
assert.equal(jsonLogic.apply(rules.customerAge, {}), 0);
});
test("in returns false when the array is absent", () => {
assert.equal(evaluate(rules.isGiftOrder, { items: ["gift", "book"] }), true);
assert.equal(evaluate(rules.isGiftOrder, { items: ["book"] }), false);
assert.equal(evaluate(rules.isGiftOrder, {}), false);
});
test("if picks the first matching branch", () => {
assert.equal(jsonLogic.apply(rules.shipping, { isDigital: true, total: 5 }), "digital-delivery");
assert.equal(jsonLogic.apply(rules.shipping, { total: 150 }), "free-shipping");
assert.equal(jsonLogic.apply(rules.shipping, { total: 20 }), "paid-shipping");
});
test("custom operation applies tiered volume discount", () => {
assert.equal(jsonLogic.apply(volumeDiscountRule, { quantity: 120, unitPrice: 10 }), 960);
assert.equal(jsonLogic.apply(volumeDiscountRule, { quantity: 10, unitPrice: 10 }), 90);
assert.equal(jsonLogic.apply(volumeDiscountRule, { quantity: 3, unitPrice: 10 }), 30);
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.