standard-rule-engine
Version:
A simple rule engine that uses Standard Schema to validate facts
205 lines (201 loc) • 5.87 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
Engine: () => Engine
});
module.exports = __toCommonJS(index_exports);
// src/utils.ts
var isNotEmpty = (obj) => {
if (!obj) return false;
for (const x in obj) return true;
return false;
};
var isClass = (v) => typeof v === "function" && /^\s*class\s+/.test(v.toString()) || // Handle Object.create(null)
v.toString && // Handle import * as Sentry from '@sentry/bun'
// This also handle [object Date], [object Array]
// and FFI value like [object Prisma]
v.toString().startsWith("[object ") && v.toString() !== "[object Object]" || // If object prototype is not pure, then probably a class-like object
isNotEmpty(Object.getPrototypeOf(v));
var isObject = (item) => item && typeof item === "object" && !Array.isArray(item);
var mergeDeep = (target, source, {
skipKeys,
override = true
} = {}) => {
if (!isObject(target) || !isObject(source)) return target;
for (const [key, value] of Object.entries(source)) {
if (skipKeys?.includes(key)) continue;
if (!isObject(value) || !(key in target) || isClass(value)) {
if (override || !(key in target))
target[key] = value;
continue;
}
target[key] = mergeDeep(
target[key],
value,
{ skipKeys, override }
);
}
return target;
};
function standardValidate(schema, input) {
let result = schema["~standard"].validate(input);
if (result instanceof Promise) {
throw new Error("Facts input must be synchronous");
}
if (result.issues) {
return {
success: false,
issues: result.issues,
data: null
};
}
return {
success: true,
issues: null,
data: result.value
};
}
// src/index.ts
var import_clone = __toESM(require("clone"), 1);
var Session = class {
constructor(context, rules, helpers = {}) {
this.context = context;
this.rules = rules;
this.wrappedHelpers = Object.entries(helpers).reduce(
(acc, [key, fn]) => {
acc[key] = (...args) => {
return fn(context, ...args);
};
return acc;
},
{}
);
}
insertedFacts = [];
wrappedHelpers;
insert(facts) {
this.insertedFacts.push(facts);
return this;
}
insertMany(facts) {
this.insertedFacts.push(...facts);
return this;
}
fire() {
for (const facts of this.insertedFacts) {
Object.freeze(facts);
for (const rule of this.rules) {
if (!rule.schema) {
rule.handler(facts, {
context: this.context,
helpers: this.wrappedHelpers
});
continue;
}
const validationResult = standardValidate(rule.schema, facts);
if (!validationResult.success) {
continue;
}
rule.handler(validationResult.data, {
context: this.context,
helpers: this.wrappedHelpers
});
}
}
return this;
}
};
var Engine = class {
"~types" = {
Singleton: {}
};
initialContext = {};
rules = [];
globalSchema;
helpers = {};
schema(schema) {
this.globalSchema = schema;
return this;
}
context(nameOrContext, value) {
if (value === void 0 && typeof nameOrContext === "object") {
this.initialContext = mergeDeep(this.initialContext, nameOrContext);
} else if (typeof nameOrContext === "string") {
this.initialContext = mergeDeep(this.initialContext, {
[nameOrContext]: value
});
}
return this;
}
helper(name, fn) {
this.helpers = {
...this.helpers,
[name]: fn
};
return this;
}
rule(name, handler, meta) {
const newRule = {
name,
handler,
priority: meta?.priority ?? 1,
schema: meta?.schema ?? this.globalSchema
};
this.rules.push(newRule);
this.rules.sort((a, b) => {
if (a.priority !== b.priority) {
return a.priority - b.priority;
}
return a.name.localeCompare(b.name);
});
return this;
}
use(instance) {
this.rules = [...this.rules, ...instance.rules];
this.initialContext = mergeDeep(
this.initialContext,
instance.initialContext
);
this.helpers = mergeDeep(this.helpers, instance.helpers);
return this;
}
createSession() {
return new Session(
(0, import_clone.default)(this.initialContext),
this.rules,
this.helpers
);
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Engine
});