@shepherdnerds/json-rules-engine 7.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +3 -0
- package/.claude/settings.local.json +24 -0
- package/.github/workflows/deploy.yml +45 -0
- package/.github/workflows/node.js.yml +28 -0
- package/CHANGELOG.md +167 -0
- package/LICENSE +15 -0
- package/README.md +235 -0
- package/dist/almanac.js +269 -0
- package/dist/condition.js +331 -0
- package/dist/debug.js +18 -0
- package/dist/engine-default-operator-decorators.js +42 -0
- package/dist/engine-default-operators.js +50 -0
- package/dist/engine.js +451 -0
- package/dist/errors.js +32 -0
- package/dist/fact.js +129 -0
- package/dist/index.js +3 -0
- package/dist/json-rules-engine.js +48 -0
- package/dist/operator-decorator.js +60 -0
- package/dist/operator-map.js +178 -0
- package/dist/operator.js +50 -0
- package/dist/rule-result.js +80 -0
- package/dist/rule.js +525 -0
- package/dist/scoped-almanac.js +120 -0
- package/package.json +96 -0
- package/types/index.d.ts +312 -0
package/package.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shepherdnerds/json-rules-engine",
|
|
3
|
+
"version": "7.3.1",
|
|
4
|
+
"description": "Rules Engine expressed in simple json",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "types/index.d.ts",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18.0.0"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/withshepherd/nested-json-rules-engine"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"rules",
|
|
16
|
+
"engine",
|
|
17
|
+
"rules engine"
|
|
18
|
+
],
|
|
19
|
+
"standard": {
|
|
20
|
+
"parser": "babel-eslint",
|
|
21
|
+
"ignore": [
|
|
22
|
+
"/dist",
|
|
23
|
+
"/examples/node_modules"
|
|
24
|
+
],
|
|
25
|
+
"globals": [
|
|
26
|
+
"context",
|
|
27
|
+
"xcontext",
|
|
28
|
+
"describe",
|
|
29
|
+
"xdescribe",
|
|
30
|
+
"it",
|
|
31
|
+
"xit",
|
|
32
|
+
"before",
|
|
33
|
+
"beforeEach",
|
|
34
|
+
"expect",
|
|
35
|
+
"factories"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"mocha": {
|
|
39
|
+
"require": [
|
|
40
|
+
"babel-core/register",
|
|
41
|
+
"babel-polyfill"
|
|
42
|
+
],
|
|
43
|
+
"file": "./test/support/bootstrap.js",
|
|
44
|
+
"checkLeaks": true,
|
|
45
|
+
"recursive": true,
|
|
46
|
+
"globals": [
|
|
47
|
+
"expect"
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"author": "Cache Hamm <cache.hamm@gmail.com>",
|
|
51
|
+
"contributors": [
|
|
52
|
+
"Chris Pardy <chris.pardy@gmail.com>"
|
|
53
|
+
],
|
|
54
|
+
"license": "ISC",
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/withshepherd/nested-json-rules-engine/issues"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/withshepherd/nested-json-rules-engine",
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"babel-cli": "6.26.0",
|
|
61
|
+
"babel-core": "6.26.3",
|
|
62
|
+
"babel-eslint": "10.1.0",
|
|
63
|
+
"babel-loader": "8.2.2",
|
|
64
|
+
"babel-polyfill": "6.26.0",
|
|
65
|
+
"babel-preset-es2015": "~6.24.1",
|
|
66
|
+
"babel-preset-stage-0": "~6.24.1",
|
|
67
|
+
"babel-register": "6.26.0",
|
|
68
|
+
"chai": "^4.3.4",
|
|
69
|
+
"chai-as-promised": "^7.1.1",
|
|
70
|
+
"colors": "~1.4.0",
|
|
71
|
+
"dirty-chai": "2.0.1",
|
|
72
|
+
"lodash": "4.17.21",
|
|
73
|
+
"mocha": "^8.4.0",
|
|
74
|
+
"perfy": "^1.1.5",
|
|
75
|
+
"sinon": "^11.1.1",
|
|
76
|
+
"sinon-chai": "^3.7.0",
|
|
77
|
+
"snazzy": "^9.0.0",
|
|
78
|
+
"standard": "^16.0.3",
|
|
79
|
+
"tsd": "^0.17.0"
|
|
80
|
+
},
|
|
81
|
+
"dependencies": {
|
|
82
|
+
"clone": "^2.1.2",
|
|
83
|
+
"eventemitter2": "^6.4.4",
|
|
84
|
+
"hash-it": "^6.0.0",
|
|
85
|
+
"jsonpath-plus": "^10.3.0"
|
|
86
|
+
},
|
|
87
|
+
"scripts": {
|
|
88
|
+
"test": "mocha && npm run lint --silent && npm run test:types",
|
|
89
|
+
"test:types": "tsd",
|
|
90
|
+
"lint": "standard --verbose --env mocha | snazzy || true",
|
|
91
|
+
"lint:fix": "standard --fix --env mocha",
|
|
92
|
+
"build": "babel --stage 1 -d dist/ src/",
|
|
93
|
+
"watch": "babel --watch --stage 1 -d dist/ src",
|
|
94
|
+
"examples": "./test/support/example_runner.sh"
|
|
95
|
+
}
|
|
96
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
export interface AlmanacOptions {
|
|
2
|
+
allowUndefinedFacts?: boolean;
|
|
3
|
+
pathResolver?: PathResolver;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface EngineOptions extends AlmanacOptions {
|
|
7
|
+
allowUndefinedConditions?: boolean;
|
|
8
|
+
replaceFactsInEventParams?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RunOptions {
|
|
12
|
+
almanac?: Almanac;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface EngineResult {
|
|
16
|
+
events: Event[];
|
|
17
|
+
failureEvents: Event[];
|
|
18
|
+
almanac: Almanac;
|
|
19
|
+
results: RuleResult[];
|
|
20
|
+
failureResults: RuleResult[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function engineFactory(
|
|
24
|
+
rules: Array<RuleProperties>,
|
|
25
|
+
options?: EngineOptions
|
|
26
|
+
): Engine;
|
|
27
|
+
|
|
28
|
+
export class Engine {
|
|
29
|
+
constructor(rules?: Array<RuleProperties>, options?: EngineOptions);
|
|
30
|
+
|
|
31
|
+
addRule(rule: RuleProperties): this;
|
|
32
|
+
removeRule(ruleOrName: Rule | string): boolean;
|
|
33
|
+
updateRule(rule: Rule): void;
|
|
34
|
+
|
|
35
|
+
setCondition(name: string, conditions: TopLevelCondition): this;
|
|
36
|
+
removeCondition(name: string): boolean;
|
|
37
|
+
|
|
38
|
+
addOperator(operator: Operator): void;
|
|
39
|
+
addOperator<A, B>(
|
|
40
|
+
operatorName: string,
|
|
41
|
+
callback: OperatorEvaluator<A, B>
|
|
42
|
+
): void;
|
|
43
|
+
removeOperator(operator: Operator | string): boolean;
|
|
44
|
+
|
|
45
|
+
addOperatorDecorator(decorator: OperatorDecorator): void;
|
|
46
|
+
addOperatorDecorator<A, B, NextA, NextB>(decoratorName: string, callback: OperatorDecoratorEvaluator<A, B, NextA, NextB>): void;
|
|
47
|
+
removeOperatorDecorator(decorator: OperatorDecorator | string): boolean;
|
|
48
|
+
|
|
49
|
+
addFact<T>(fact: Fact<T>): this;
|
|
50
|
+
addFact<T>(
|
|
51
|
+
id: string,
|
|
52
|
+
valueCallback: DynamicFactCallback<T> | T,
|
|
53
|
+
options?: FactOptions
|
|
54
|
+
): this;
|
|
55
|
+
removeFact(factOrId: string | Fact): boolean;
|
|
56
|
+
getFact<T>(factId: string): Fact<T>;
|
|
57
|
+
|
|
58
|
+
on<T = Event>(eventName: string, handler: EventHandler<T>): this;
|
|
59
|
+
|
|
60
|
+
run(facts?: Record<string, any>, runOptions?: RunOptions): Promise<EngineResult>;
|
|
61
|
+
stop(): this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface OperatorEvaluator<A, B> {
|
|
65
|
+
(factValue: A, compareToValue: B): boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class Operator<A = unknown, B = unknown> {
|
|
69
|
+
public name: string;
|
|
70
|
+
constructor(
|
|
71
|
+
name: string,
|
|
72
|
+
evaluator: OperatorEvaluator<A, B>,
|
|
73
|
+
validator?: (factValue: A) => boolean
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface OperatorDecoratorEvaluator<A, B, NextA, NextB> {
|
|
78
|
+
(factValue: A, compareToValue: B, next: OperatorEvaluator<NextA, NextB>): boolean
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export class OperatorDecorator<A = unknown, B = unknown, NextA = unknown, NextB = unknown> {
|
|
82
|
+
public name: string;
|
|
83
|
+
constructor(
|
|
84
|
+
name: string,
|
|
85
|
+
evaluator: OperatorDecoratorEvaluator<A, B, NextA, NextB>,
|
|
86
|
+
validator?: (factValue: A) => boolean
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export class Almanac {
|
|
91
|
+
constructor(options?: AlmanacOptions);
|
|
92
|
+
factValue<T>(
|
|
93
|
+
factId: string,
|
|
94
|
+
params?: Record<string, any>,
|
|
95
|
+
path?: string
|
|
96
|
+
): Promise<T>;
|
|
97
|
+
/**
|
|
98
|
+
* Resolves a path - only valid in scoped context (ScopedAlmanac)
|
|
99
|
+
* Throws error when called on regular Almanac to catch misuse of scoped conditions
|
|
100
|
+
*/
|
|
101
|
+
resolvePath<T>(path: string): Promise<T>;
|
|
102
|
+
addFact<T>(fact: Fact<T>): this;
|
|
103
|
+
addFact<T>(
|
|
104
|
+
id: string,
|
|
105
|
+
valueCallback: DynamicFactCallback<T> | T,
|
|
106
|
+
options?: FactOptions
|
|
107
|
+
): this;
|
|
108
|
+
addRuntimeFact(factId: string, value: any): void;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Scoped Almanac for nested condition evaluation
|
|
113
|
+
* Wraps a parent almanac but prioritizes item properties for fact resolution
|
|
114
|
+
*/
|
|
115
|
+
export class ScopedAlmanac {
|
|
116
|
+
constructor(parentAlmanac: Almanac, item: any);
|
|
117
|
+
/**
|
|
118
|
+
* Resolves a path directly on the current scoped item
|
|
119
|
+
* Used by scoped conditions that have path but no fact
|
|
120
|
+
*/
|
|
121
|
+
resolvePath<T>(path: string): Promise<T>;
|
|
122
|
+
factValue<T>(
|
|
123
|
+
factId: string,
|
|
124
|
+
params?: Record<string, any>,
|
|
125
|
+
path?: string
|
|
126
|
+
): Promise<T>;
|
|
127
|
+
getValue<T>(value: any): Promise<T>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type FactOptions = {
|
|
131
|
+
cache?: boolean;
|
|
132
|
+
priority?: number;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export type DynamicFactCallback<T = unknown> = (
|
|
136
|
+
params: Record<string, any>,
|
|
137
|
+
almanac: Almanac
|
|
138
|
+
) => T;
|
|
139
|
+
|
|
140
|
+
export class Fact<T = unknown> {
|
|
141
|
+
id: string;
|
|
142
|
+
priority: number;
|
|
143
|
+
options: FactOptions;
|
|
144
|
+
value?: T;
|
|
145
|
+
calculationMethod?: DynamicFactCallback<T>;
|
|
146
|
+
|
|
147
|
+
constructor(
|
|
148
|
+
id: string,
|
|
149
|
+
value: T | DynamicFactCallback<T>,
|
|
150
|
+
options?: FactOptions
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface Event {
|
|
155
|
+
type: string;
|
|
156
|
+
params?: Record<string, any>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export type PathResolver = (value: object, path: string) => any;
|
|
160
|
+
|
|
161
|
+
export type EventHandler<T = Event> = (
|
|
162
|
+
event: T,
|
|
163
|
+
almanac: Almanac,
|
|
164
|
+
ruleResult: RuleResult
|
|
165
|
+
) => void;
|
|
166
|
+
|
|
167
|
+
export interface RuleProperties {
|
|
168
|
+
conditions: TopLevelCondition;
|
|
169
|
+
event: Event;
|
|
170
|
+
name?: string;
|
|
171
|
+
priority?: number;
|
|
172
|
+
onSuccess?: EventHandler;
|
|
173
|
+
onFailure?: EventHandler;
|
|
174
|
+
}
|
|
175
|
+
export type RuleSerializable = Pick<
|
|
176
|
+
Required<RuleProperties>,
|
|
177
|
+
"conditions" | "event" | "name" | "priority"
|
|
178
|
+
>;
|
|
179
|
+
|
|
180
|
+
export type RuleResultSerializable = Pick<
|
|
181
|
+
Required<RuleResult>,
|
|
182
|
+
"name" | "event" | "priority" | "result"> & {
|
|
183
|
+
conditions: TopLevelConditionResultSerializable
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export interface RuleResult {
|
|
187
|
+
name: string;
|
|
188
|
+
conditions: TopLevelConditionResult;
|
|
189
|
+
event?: Event;
|
|
190
|
+
priority?: number;
|
|
191
|
+
result: any;
|
|
192
|
+
toJSON(): string;
|
|
193
|
+
toJSON<T extends boolean>(
|
|
194
|
+
stringify: T
|
|
195
|
+
): T extends true ? string : RuleResultSerializable;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export class Rule implements RuleProperties {
|
|
199
|
+
constructor(ruleProps: RuleProperties | string);
|
|
200
|
+
name: string;
|
|
201
|
+
conditions: TopLevelCondition;
|
|
202
|
+
/**
|
|
203
|
+
* @deprecated Use {@link Rule.event} instead.
|
|
204
|
+
*/
|
|
205
|
+
ruleEvent: Event;
|
|
206
|
+
event: Event
|
|
207
|
+
priority: number;
|
|
208
|
+
setConditions(conditions: TopLevelCondition): this;
|
|
209
|
+
setEvent(event: Event): this;
|
|
210
|
+
setPriority(priority: number): this;
|
|
211
|
+
toJSON(): string;
|
|
212
|
+
toJSON<T extends boolean>(
|
|
213
|
+
stringify: T
|
|
214
|
+
): T extends true ? string : RuleSerializable;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
interface BooleanConditionResultProperties {
|
|
218
|
+
result?: boolean
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
interface ConditionResultProperties extends BooleanConditionResultProperties {
|
|
222
|
+
factResult?: unknown
|
|
223
|
+
valueResult?: unknown
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
interface ConditionProperties {
|
|
227
|
+
fact: string;
|
|
228
|
+
operator: string;
|
|
229
|
+
value: { fact: string } | any;
|
|
230
|
+
path?: string;
|
|
231
|
+
priority?: number;
|
|
232
|
+
params?: Record<string, any>;
|
|
233
|
+
name?: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Scoped condition that evaluates a path directly on the current array item
|
|
238
|
+
* Used inside nested conditions where the "fact" is implicitly the current array item
|
|
239
|
+
* The path is resolved using JSONPath against the scoped item
|
|
240
|
+
*/
|
|
241
|
+
interface ScopedConditionProperties {
|
|
242
|
+
path: string;
|
|
243
|
+
operator: string;
|
|
244
|
+
value: { path: string } | any;
|
|
245
|
+
priority?: number;
|
|
246
|
+
params?: Record<string, any>;
|
|
247
|
+
name?: string;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
interface ScopedConditionPropertiesResult extends ScopedConditionProperties, ConditionResultProperties {}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Nested condition that evaluates conditions against array items
|
|
254
|
+
* Uses the 'some' operator to check if at least one array item matches
|
|
255
|
+
*/
|
|
256
|
+
interface NestedConditionProperties {
|
|
257
|
+
fact: string;
|
|
258
|
+
operator: 'some';
|
|
259
|
+
conditions: TopLevelCondition;
|
|
260
|
+
path?: string;
|
|
261
|
+
priority?: number;
|
|
262
|
+
params?: Record<string, any>;
|
|
263
|
+
name?: string;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
interface NestedConditionPropertiesResult extends NestedConditionProperties, ConditionResultProperties {
|
|
267
|
+
conditions: TopLevelConditionResult;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
type ConditionPropertiesResult = ConditionProperties & ConditionResultProperties
|
|
271
|
+
|
|
272
|
+
type NestedCondition = ConditionProperties | NestedConditionProperties | ScopedConditionProperties | TopLevelCondition;
|
|
273
|
+
type NestedConditionResult = ConditionPropertiesResult | NestedConditionPropertiesResult | ScopedConditionPropertiesResult | TopLevelConditionResult;
|
|
274
|
+
type AllConditions = {
|
|
275
|
+
all: NestedCondition[];
|
|
276
|
+
name?: string;
|
|
277
|
+
priority?: number;
|
|
278
|
+
};
|
|
279
|
+
type AllConditionsResult = AllConditions & {
|
|
280
|
+
all: NestedConditionResult[]
|
|
281
|
+
} & BooleanConditionResultProperties
|
|
282
|
+
type AnyConditions = {
|
|
283
|
+
any: NestedCondition[];
|
|
284
|
+
name?: string;
|
|
285
|
+
priority?: number;
|
|
286
|
+
};
|
|
287
|
+
type AnyConditionsResult = AnyConditions & {
|
|
288
|
+
any: NestedConditionResult[]
|
|
289
|
+
} & BooleanConditionResultProperties
|
|
290
|
+
type NotConditions = { not: NestedCondition; name?: string; priority?: number };
|
|
291
|
+
type NotConditionsResult = NotConditions & {not: NestedConditionResult} & BooleanConditionResultProperties;
|
|
292
|
+
type ConditionReference = {
|
|
293
|
+
condition: string;
|
|
294
|
+
name?: string;
|
|
295
|
+
priority?: number;
|
|
296
|
+
};
|
|
297
|
+
type ConditionReferenceResult = ConditionReference & BooleanConditionResultProperties
|
|
298
|
+
export type TopLevelCondition =
|
|
299
|
+
| AllConditions
|
|
300
|
+
| AnyConditions
|
|
301
|
+
| NotConditions
|
|
302
|
+
| ConditionReference;
|
|
303
|
+
export type TopLevelConditionResult =
|
|
304
|
+
| AllConditionsResult
|
|
305
|
+
| AnyConditionsResult
|
|
306
|
+
| NotConditionsResult
|
|
307
|
+
| ConditionReferenceResult
|
|
308
|
+
export type TopLevelConditionResultSerializable =
|
|
309
|
+
| AllConditionsResult
|
|
310
|
+
| AnyConditionsResult
|
|
311
|
+
| NotConditionsResult
|
|
312
|
+
| ConditionReference
|