@f-o-t/rules-engine 1.0.0 → 2.0.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/README.md +78 -20
- package/dist/index.cjs +286 -218
- package/dist/index.d.cts +191 -186
- package/dist/index.d.ts +191 -186
- package/dist/index.js +291 -218
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -37,7 +37,6 @@ __export(exports_src, {
|
|
|
37
37
|
validateRule: () => validateRule,
|
|
38
38
|
validateConditions: () => validateConditions,
|
|
39
39
|
updateRule: () => updateRule,
|
|
40
|
-
tap: () => tap,
|
|
41
40
|
str: () => str,
|
|
42
41
|
sortRules: () => sortRules,
|
|
43
42
|
sortByUpdatedAt: () => sortByUpdatedAt,
|
|
@@ -55,16 +54,18 @@ __export(exports_src, {
|
|
|
55
54
|
removeRuleSet: () => removeRuleSet,
|
|
56
55
|
removeRule: () => removeRule,
|
|
57
56
|
pruneOldVersions: () => pruneOldVersions,
|
|
58
|
-
|
|
57
|
+
parseVersioningConfig: () => parseVersioningConfig,
|
|
58
|
+
parseValidationConfig: () => parseValidationConfig,
|
|
59
59
|
parseRule: () => parseRule,
|
|
60
|
+
parseCacheConfig: () => parseCacheConfig,
|
|
60
61
|
or: () => or,
|
|
61
62
|
num: () => num,
|
|
62
63
|
mergeRuleSets: () => mergeRuleSets,
|
|
63
64
|
measureTimeAsync: () => measureTimeAsync,
|
|
64
65
|
measureTime: () => measureTime,
|
|
66
|
+
isConditionGroup: () => import_condition_evaluator7.isConditionGroup,
|
|
65
67
|
importRules: () => importRules,
|
|
66
68
|
importFromJson: () => importFromJson,
|
|
67
|
-
identity: () => identity,
|
|
68
69
|
hashRules: () => hashRules,
|
|
69
70
|
hashContext: () => hashContext,
|
|
70
71
|
hasErrors: () => hasErrors,
|
|
@@ -95,6 +96,11 @@ __export(exports_src, {
|
|
|
95
96
|
getLatestVersion: () => getLatestVersion,
|
|
96
97
|
getIndexStats: () => getIndexStats,
|
|
97
98
|
getHistory: () => getHistory,
|
|
99
|
+
getDefaultVersioningConfig: () => getDefaultVersioningConfig,
|
|
100
|
+
getDefaultValidationConfig: () => getDefaultValidationConfig,
|
|
101
|
+
getDefaultLogLevel: () => getDefaultLogLevel,
|
|
102
|
+
getDefaultConflictResolution: () => getDefaultConflictResolution,
|
|
103
|
+
getDefaultCacheConfig: () => getDefaultCacheConfig,
|
|
98
104
|
getConflictsByType: () => getConflictsByType,
|
|
99
105
|
getConflictsBySeverity: () => getConflictsBySeverity,
|
|
100
106
|
getAllVersions: () => getAllVersions,
|
|
@@ -123,7 +129,6 @@ __export(exports_src, {
|
|
|
123
129
|
detectConflicts: () => detectConflicts,
|
|
124
130
|
deserializeRuleSet: () => deserializeRuleSet,
|
|
125
131
|
deserializeRule: () => deserializeRule,
|
|
126
|
-
delay: () => delay,
|
|
127
132
|
date: () => date,
|
|
128
133
|
createVersionStore: () => createVersionStore,
|
|
129
134
|
createRuleValidator: () => createRuleValidator,
|
|
@@ -135,7 +140,6 @@ __export(exports_src, {
|
|
|
135
140
|
createEngine: () => createEngine,
|
|
136
141
|
createCache: () => createCache,
|
|
137
142
|
conditions: () => conditions,
|
|
138
|
-
compose: () => compose,
|
|
139
143
|
compareVersions: () => compareVersions,
|
|
140
144
|
cloneState: () => cloneState,
|
|
141
145
|
cloneRule: () => cloneRule,
|
|
@@ -154,23 +158,50 @@ __export(exports_src, {
|
|
|
154
158
|
analyzeOperatorUsage: () => analyzeOperatorUsage,
|
|
155
159
|
analyzeFieldUsage: () => analyzeFieldUsage,
|
|
156
160
|
analyzeConsequenceUsage: () => analyzeConsequenceUsage,
|
|
157
|
-
always: () => always,
|
|
158
161
|
all: () => all,
|
|
159
162
|
addVersion: () => addVersion,
|
|
160
163
|
addRules: () => addRules,
|
|
161
164
|
addRuleSet: () => addRuleSet,
|
|
162
165
|
addRule: () => addRule,
|
|
166
|
+
VersioningConfigSchema: () => VersioningConfigSchema,
|
|
167
|
+
ValidationResultSchema: () => ValidationResultSchema,
|
|
168
|
+
ValidationOptionsSchema: () => ValidationOptionsSchema,
|
|
169
|
+
ValidationErrorSchema: () => ValidationErrorSchema,
|
|
170
|
+
ValidationConfigSchema: () => ValidationConfigSchema,
|
|
171
|
+
RuleStatsSchema: () => RuleStatsSchema,
|
|
163
172
|
RuleSetSchema: () => RuleSetSchema,
|
|
164
173
|
RuleSchema: () => RuleSchema,
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
174
|
+
LogLevelSchema: () => LogLevelSchema,
|
|
175
|
+
EvaluateOptionsSchema: () => EvaluateOptionsSchema,
|
|
176
|
+
EvaluateConfigSchema: () => EvaluateConfigSchema,
|
|
177
|
+
EngineStatsSchema: () => EngineStatsSchema,
|
|
178
|
+
ConflictResolutionStrategySchema: () => ConflictResolutionStrategySchema,
|
|
179
|
+
ConditionGroupSchema: () => import_condition_evaluator7.ConditionGroup,
|
|
180
|
+
CacheStatsSchema: () => CacheStatsSchema,
|
|
181
|
+
CacheConfigSchema: () => CacheConfigSchema
|
|
169
182
|
});
|
|
170
183
|
module.exports = __toCommonJS(exports_src);
|
|
184
|
+
var import_condition_evaluator7 = require("@f-o-t/condition-evaluator");
|
|
171
185
|
|
|
172
186
|
// src/analyzer/analysis.ts
|
|
187
|
+
var import_condition_evaluator2 = require("@f-o-t/condition-evaluator");
|
|
188
|
+
|
|
189
|
+
// src/utils/conditions.ts
|
|
173
190
|
var import_condition_evaluator = require("@f-o-t/condition-evaluator");
|
|
191
|
+
var collectConditionFields = (condition) => {
|
|
192
|
+
const fields = new Set;
|
|
193
|
+
const traverse = (c) => {
|
|
194
|
+
if (import_condition_evaluator.isConditionGroup(c)) {
|
|
195
|
+
for (const child of c.conditions) {
|
|
196
|
+
traverse(child);
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
fields.add(c.field);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
traverse(condition);
|
|
203
|
+
return fields;
|
|
204
|
+
};
|
|
174
205
|
var countConditions = (condition) => {
|
|
175
206
|
if (import_condition_evaluator.isConditionGroup(condition)) {
|
|
176
207
|
return condition.conditions.reduce((sum, c) => sum + countConditions(c), 0);
|
|
@@ -185,30 +216,18 @@ var calculateMaxDepth = (condition, currentDepth = 1) => {
|
|
|
185
216
|
}
|
|
186
217
|
return currentDepth;
|
|
187
218
|
};
|
|
188
|
-
var
|
|
219
|
+
var countConditionGroups = (condition) => {
|
|
189
220
|
if (import_condition_evaluator.isConditionGroup(condition)) {
|
|
190
|
-
return 1 + condition.conditions.reduce((sum, c) => sum +
|
|
221
|
+
return 1 + condition.conditions.reduce((sum, c) => sum + countConditionGroups(c), 0);
|
|
191
222
|
}
|
|
192
223
|
return 0;
|
|
193
224
|
};
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const traverse = (c) => {
|
|
197
|
-
if (import_condition_evaluator.isConditionGroup(c)) {
|
|
198
|
-
for (const child of c.conditions) {
|
|
199
|
-
traverse(child);
|
|
200
|
-
}
|
|
201
|
-
} else {
|
|
202
|
-
fields.add(c.field);
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
traverse(condition);
|
|
206
|
-
return fields;
|
|
207
|
-
};
|
|
225
|
+
|
|
226
|
+
// src/analyzer/analysis.ts
|
|
208
227
|
var collectOperators = (condition) => {
|
|
209
228
|
const operators = new Set;
|
|
210
229
|
const traverse = (c) => {
|
|
211
|
-
if (
|
|
230
|
+
if (import_condition_evaluator2.isConditionGroup(c)) {
|
|
212
231
|
operators.add(c.operator);
|
|
213
232
|
for (const child of c.conditions) {
|
|
214
233
|
traverse(child);
|
|
@@ -226,8 +245,8 @@ var calculateComplexityScore = (totalConditions, maxDepth, groupCount, uniqueFie
|
|
|
226
245
|
var analyzeRuleComplexity = (rule) => {
|
|
227
246
|
const totalConditions = countConditions(rule.conditions);
|
|
228
247
|
const maxDepth = calculateMaxDepth(rule.conditions);
|
|
229
|
-
const groupCount =
|
|
230
|
-
const uniqueFields =
|
|
248
|
+
const groupCount = countConditionGroups(rule.conditions);
|
|
249
|
+
const uniqueFields = collectConditionFields(rule.conditions).size;
|
|
231
250
|
const uniqueOperators = collectOperators(rule.conditions).size;
|
|
232
251
|
const consequenceCount = rule.consequences.length;
|
|
233
252
|
const complexityScore = calculateComplexityScore(totalConditions, maxDepth, groupCount, uniqueFields);
|
|
@@ -264,7 +283,7 @@ var analyzeRuleSet = (rules) => {
|
|
|
264
283
|
maxPriority = rule.priority;
|
|
265
284
|
if (rule.enabled)
|
|
266
285
|
enabledCount++;
|
|
267
|
-
for (const field of
|
|
286
|
+
for (const field of collectConditionFields(rule.conditions)) {
|
|
268
287
|
allFields.add(field);
|
|
269
288
|
}
|
|
270
289
|
for (const operator of collectOperators(rule.conditions)) {
|
|
@@ -308,7 +327,7 @@ var analyzeFieldUsage = (rules) => {
|
|
|
308
327
|
const fieldMap = new Map;
|
|
309
328
|
for (const rule of rules) {
|
|
310
329
|
const traverse = (c) => {
|
|
311
|
-
if (
|
|
330
|
+
if (import_condition_evaluator2.isConditionGroup(c)) {
|
|
312
331
|
for (const child of c.conditions) {
|
|
313
332
|
traverse(child);
|
|
314
333
|
}
|
|
@@ -340,7 +359,7 @@ var analyzeOperatorUsage = (rules) => {
|
|
|
340
359
|
const operatorMap = new Map;
|
|
341
360
|
for (const rule of rules) {
|
|
342
361
|
const traverse = (c) => {
|
|
343
|
-
if (
|
|
362
|
+
if (import_condition_evaluator2.isConditionGroup(c)) {
|
|
344
363
|
for (const child of c.conditions) {
|
|
345
364
|
traverse(child);
|
|
346
365
|
}
|
|
@@ -358,12 +377,15 @@ var analyzeOperatorUsage = (rules) => {
|
|
|
358
377
|
};
|
|
359
378
|
traverse(rule.conditions);
|
|
360
379
|
}
|
|
361
|
-
return [...operatorMap.entries()].map(([key, data]) =>
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
380
|
+
return [...operatorMap.entries()].map(([key, data]) => {
|
|
381
|
+
const parts = key.split(":");
|
|
382
|
+
return {
|
|
383
|
+
operator: parts[1] ?? "",
|
|
384
|
+
type: data.type,
|
|
385
|
+
count: data.rules.length,
|
|
386
|
+
rules: data.rules
|
|
387
|
+
};
|
|
388
|
+
}).sort((a, b) => b.count - a.count);
|
|
367
389
|
};
|
|
368
390
|
var analyzeConsequenceUsage = (rules) => {
|
|
369
391
|
const consequenceMap = new Map;
|
|
@@ -652,15 +674,8 @@ var createCache = (options) => {
|
|
|
652
674
|
const evictOldest = () => {
|
|
653
675
|
if (entries.size === 0)
|
|
654
676
|
return;
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
for (const [key, entry] of entries) {
|
|
658
|
-
if (entry.createdAt < oldestTime) {
|
|
659
|
-
oldestTime = entry.createdAt;
|
|
660
|
-
oldestKey = key;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
if (oldestKey) {
|
|
677
|
+
const oldestKey = entries.keys().next().value;
|
|
678
|
+
if (oldestKey !== undefined) {
|
|
664
679
|
const entry = entries.get(oldestKey);
|
|
665
680
|
entries.delete(oldestKey);
|
|
666
681
|
evictions++;
|
|
@@ -756,7 +771,7 @@ var createNoopCache = () => {
|
|
|
756
771
|
};
|
|
757
772
|
};
|
|
758
773
|
// src/core/evaluate.ts
|
|
759
|
-
var
|
|
774
|
+
var import_condition_evaluator3 = require("@f-o-t/condition-evaluator");
|
|
760
775
|
|
|
761
776
|
// src/utils/time.ts
|
|
762
777
|
var measureTime = (fn) => {
|
|
@@ -785,9 +800,6 @@ var withTimeout = (promise, timeoutMs, errorMessage = "Operation timed out") =>
|
|
|
785
800
|
});
|
|
786
801
|
});
|
|
787
802
|
};
|
|
788
|
-
var delay = (ms) => {
|
|
789
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
790
|
-
};
|
|
791
803
|
|
|
792
804
|
// src/core/evaluate.ts
|
|
793
805
|
var evaluateRule = (rule2, context, options = {}) => {
|
|
@@ -809,7 +821,7 @@ var evaluateRule = (rule2, context, options = {}) => {
|
|
|
809
821
|
data: context.data,
|
|
810
822
|
metadata: context.metadata
|
|
811
823
|
};
|
|
812
|
-
return
|
|
824
|
+
return import_condition_evaluator3.evaluateConditionGroup(rule2.conditions, evalContext);
|
|
813
825
|
} catch (error) {
|
|
814
826
|
return {
|
|
815
827
|
error,
|
|
@@ -1021,30 +1033,67 @@ var sortByUpdatedAt = (direction = "desc") => {
|
|
|
1021
1033
|
return sortRules({ field: "updatedAt", direction });
|
|
1022
1034
|
};
|
|
1023
1035
|
// src/types/config.ts
|
|
1024
|
-
var
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
};
|
|
1037
|
-
var
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
};
|
|
1036
|
+
var import_zod = require("zod");
|
|
1037
|
+
var LogLevelSchema = import_zod.z.enum([
|
|
1038
|
+
"none",
|
|
1039
|
+
"error",
|
|
1040
|
+
"warn",
|
|
1041
|
+
"info",
|
|
1042
|
+
"debug"
|
|
1043
|
+
]);
|
|
1044
|
+
var CacheConfigSchema = import_zod.z.object({
|
|
1045
|
+
enabled: import_zod.z.boolean().default(true),
|
|
1046
|
+
ttl: import_zod.z.number().int().positive().default(60000),
|
|
1047
|
+
maxSize: import_zod.z.number().int().positive().default(1000)
|
|
1048
|
+
});
|
|
1049
|
+
var ValidationConfigSchema = import_zod.z.object({
|
|
1050
|
+
enabled: import_zod.z.boolean().default(true),
|
|
1051
|
+
strict: import_zod.z.boolean().default(false)
|
|
1052
|
+
});
|
|
1053
|
+
var VersioningConfigSchema = import_zod.z.object({
|
|
1054
|
+
enabled: import_zod.z.boolean().default(false),
|
|
1055
|
+
maxVersions: import_zod.z.number().int().positive().default(10)
|
|
1056
|
+
});
|
|
1057
|
+
var parseCacheConfig = (input) => CacheConfigSchema.parse(input ?? {});
|
|
1058
|
+
var parseValidationConfig = (input) => ValidationConfigSchema.parse(input ?? {});
|
|
1059
|
+
var parseVersioningConfig = (input) => VersioningConfigSchema.parse(input ?? {});
|
|
1060
|
+
var getDefaultCacheConfig = () => CacheConfigSchema.parse({});
|
|
1061
|
+
var getDefaultValidationConfig = () => ValidationConfigSchema.parse({});
|
|
1062
|
+
var getDefaultVersioningConfig = () => VersioningConfigSchema.parse({});
|
|
1063
|
+
var getDefaultLogLevel = () => "warn";
|
|
1064
|
+
var getDefaultConflictResolution = () => "priority";
|
|
1046
1065
|
|
|
1047
1066
|
// src/types/state.ts
|
|
1067
|
+
var import_zod2 = require("zod");
|
|
1068
|
+
var RuleStatsSchema = import_zod2.z.object({
|
|
1069
|
+
evaluations: import_zod2.z.number().int().nonnegative(),
|
|
1070
|
+
matches: import_zod2.z.number().int().nonnegative(),
|
|
1071
|
+
errors: import_zod2.z.number().int().nonnegative(),
|
|
1072
|
+
totalTimeMs: import_zod2.z.number().nonnegative(),
|
|
1073
|
+
avgTimeMs: import_zod2.z.number().nonnegative(),
|
|
1074
|
+
lastEvaluated: import_zod2.z.date().optional()
|
|
1075
|
+
});
|
|
1076
|
+
var CacheStatsSchema = import_zod2.z.object({
|
|
1077
|
+
size: import_zod2.z.number().int().nonnegative(),
|
|
1078
|
+
maxSize: import_zod2.z.number().int().positive(),
|
|
1079
|
+
hits: import_zod2.z.number().int().nonnegative(),
|
|
1080
|
+
misses: import_zod2.z.number().int().nonnegative(),
|
|
1081
|
+
hitRate: import_zod2.z.number().min(0).max(1),
|
|
1082
|
+
evictions: import_zod2.z.number().int().nonnegative()
|
|
1083
|
+
});
|
|
1084
|
+
var EngineStatsSchema = import_zod2.z.object({
|
|
1085
|
+
totalRules: import_zod2.z.number().int().nonnegative(),
|
|
1086
|
+
enabledRules: import_zod2.z.number().int().nonnegative(),
|
|
1087
|
+
disabledRules: import_zod2.z.number().int().nonnegative(),
|
|
1088
|
+
totalRuleSets: import_zod2.z.number().int().nonnegative(),
|
|
1089
|
+
totalEvaluations: import_zod2.z.number().int().nonnegative(),
|
|
1090
|
+
totalMatches: import_zod2.z.number().int().nonnegative(),
|
|
1091
|
+
totalErrors: import_zod2.z.number().int().nonnegative(),
|
|
1092
|
+
avgEvaluationTimeMs: import_zod2.z.number().nonnegative(),
|
|
1093
|
+
cacheHits: import_zod2.z.number().int().nonnegative(),
|
|
1094
|
+
cacheMisses: import_zod2.z.number().int().nonnegative(),
|
|
1095
|
+
cacheHitRate: import_zod2.z.number().min(0).max(1)
|
|
1096
|
+
});
|
|
1048
1097
|
var createInitialState = () => ({
|
|
1049
1098
|
rules: new Map,
|
|
1050
1099
|
ruleSets: new Map,
|
|
@@ -1101,82 +1150,73 @@ var generateId = () => {
|
|
|
1101
1150
|
};
|
|
1102
1151
|
|
|
1103
1152
|
// src/engine/hooks.ts
|
|
1104
|
-
var
|
|
1153
|
+
var toError = (error) => error instanceof Error ? error : new Error(String(error));
|
|
1154
|
+
var executeWithTimeout = async (hookName, hookFn, hooks, timeoutMs) => {
|
|
1155
|
+
try {
|
|
1156
|
+
const promise = Promise.resolve(hookFn());
|
|
1157
|
+
if (timeoutMs !== undefined && timeoutMs > 0) {
|
|
1158
|
+
await withTimeout(promise, timeoutMs, `Hook '${hookName}' timed out after ${timeoutMs}ms`);
|
|
1159
|
+
} else {
|
|
1160
|
+
await promise;
|
|
1161
|
+
}
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
hooks.onHookError?.(hookName, toError(error));
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
var executeBeforeEvaluation = async (hooks, context, rules, timeoutMs) => {
|
|
1105
1167
|
if (!hooks.beforeEvaluation)
|
|
1106
1168
|
return;
|
|
1107
|
-
|
|
1108
|
-
await hooks.beforeEvaluation(context, rules);
|
|
1109
|
-
} catch {}
|
|
1169
|
+
await executeWithTimeout("beforeEvaluation", () => hooks.beforeEvaluation(context, rules), hooks, timeoutMs);
|
|
1110
1170
|
};
|
|
1111
|
-
var executeAfterEvaluation = async (hooks, result) => {
|
|
1171
|
+
var executeAfterEvaluation = async (hooks, result, timeoutMs) => {
|
|
1112
1172
|
if (!hooks.afterEvaluation)
|
|
1113
1173
|
return;
|
|
1114
|
-
|
|
1115
|
-
await hooks.afterEvaluation(result);
|
|
1116
|
-
} catch {}
|
|
1174
|
+
await executeWithTimeout("afterEvaluation", () => hooks.afterEvaluation(result), hooks, timeoutMs);
|
|
1117
1175
|
};
|
|
1118
|
-
var executeBeforeRuleEvaluation = async (hooks, rule2, context) => {
|
|
1176
|
+
var executeBeforeRuleEvaluation = async (hooks, rule2, context, timeoutMs) => {
|
|
1119
1177
|
if (!hooks.beforeRuleEvaluation)
|
|
1120
1178
|
return;
|
|
1121
|
-
|
|
1122
|
-
await hooks.beforeRuleEvaluation(rule2, context);
|
|
1123
|
-
} catch {}
|
|
1179
|
+
await executeWithTimeout("beforeRuleEvaluation", () => hooks.beforeRuleEvaluation(rule2, context), hooks, timeoutMs);
|
|
1124
1180
|
};
|
|
1125
|
-
var executeAfterRuleEvaluation = async (hooks, rule2, result) => {
|
|
1181
|
+
var executeAfterRuleEvaluation = async (hooks, rule2, result, timeoutMs) => {
|
|
1126
1182
|
if (!hooks.afterRuleEvaluation)
|
|
1127
1183
|
return;
|
|
1128
|
-
|
|
1129
|
-
await hooks.afterRuleEvaluation(rule2, result);
|
|
1130
|
-
} catch {}
|
|
1184
|
+
await executeWithTimeout("afterRuleEvaluation", () => hooks.afterRuleEvaluation(rule2, result), hooks, timeoutMs);
|
|
1131
1185
|
};
|
|
1132
|
-
var executeOnRuleMatch = async (hooks, rule2, context) => {
|
|
1186
|
+
var executeOnRuleMatch = async (hooks, rule2, context, timeoutMs) => {
|
|
1133
1187
|
if (!hooks.onRuleMatch)
|
|
1134
1188
|
return;
|
|
1135
|
-
|
|
1136
|
-
await hooks.onRuleMatch(rule2, context);
|
|
1137
|
-
} catch {}
|
|
1189
|
+
await executeWithTimeout("onRuleMatch", () => hooks.onRuleMatch(rule2, context), hooks, timeoutMs);
|
|
1138
1190
|
};
|
|
1139
|
-
var executeOnRuleSkip = async (hooks, rule2, reason) => {
|
|
1191
|
+
var executeOnRuleSkip = async (hooks, rule2, reason, timeoutMs) => {
|
|
1140
1192
|
if (!hooks.onRuleSkip)
|
|
1141
1193
|
return;
|
|
1142
|
-
|
|
1143
|
-
await hooks.onRuleSkip(rule2, reason);
|
|
1144
|
-
} catch {}
|
|
1194
|
+
await executeWithTimeout("onRuleSkip", () => hooks.onRuleSkip(rule2, reason), hooks, timeoutMs);
|
|
1145
1195
|
};
|
|
1146
|
-
var executeOnRuleError = async (hooks, rule2,
|
|
1196
|
+
var executeOnRuleError = async (hooks, rule2, ruleError, timeoutMs) => {
|
|
1147
1197
|
if (!hooks.onRuleError)
|
|
1148
1198
|
return;
|
|
1149
|
-
|
|
1150
|
-
await hooks.onRuleError(rule2, error);
|
|
1151
|
-
} catch {}
|
|
1199
|
+
await executeWithTimeout("onRuleError", () => hooks.onRuleError(rule2, ruleError), hooks, timeoutMs);
|
|
1152
1200
|
};
|
|
1153
|
-
var executeOnConsequenceCollected = async (hooks, rule2, consequence) => {
|
|
1201
|
+
var executeOnConsequenceCollected = async (hooks, rule2, consequence, timeoutMs) => {
|
|
1154
1202
|
if (!hooks.onConsequenceCollected)
|
|
1155
1203
|
return;
|
|
1156
|
-
|
|
1157
|
-
await hooks.onConsequenceCollected(rule2, consequence);
|
|
1158
|
-
} catch {}
|
|
1204
|
+
await executeWithTimeout("onConsequenceCollected", () => hooks.onConsequenceCollected(rule2, consequence), hooks, timeoutMs);
|
|
1159
1205
|
};
|
|
1160
|
-
var executeOnCacheHit = async (hooks, key, result) => {
|
|
1206
|
+
var executeOnCacheHit = async (hooks, key, result, timeoutMs) => {
|
|
1161
1207
|
if (!hooks.onCacheHit)
|
|
1162
1208
|
return;
|
|
1163
|
-
|
|
1164
|
-
await hooks.onCacheHit(key, result);
|
|
1165
|
-
} catch {}
|
|
1209
|
+
await executeWithTimeout("onCacheHit", () => hooks.onCacheHit(key, result), hooks, timeoutMs);
|
|
1166
1210
|
};
|
|
1167
|
-
var executeOnCacheMiss = async (hooks, key) => {
|
|
1211
|
+
var executeOnCacheMiss = async (hooks, key, timeoutMs) => {
|
|
1168
1212
|
if (!hooks.onCacheMiss)
|
|
1169
1213
|
return;
|
|
1170
|
-
|
|
1171
|
-
await hooks.onCacheMiss(key);
|
|
1172
|
-
} catch {}
|
|
1214
|
+
await executeWithTimeout("onCacheMiss", () => hooks.onCacheMiss(key), hooks, timeoutMs);
|
|
1173
1215
|
};
|
|
1174
|
-
var executeOnSlowRule = async (hooks, rule2, timeMs, threshold) => {
|
|
1216
|
+
var executeOnSlowRule = async (hooks, rule2, timeMs, threshold, timeoutMs) => {
|
|
1175
1217
|
if (!hooks.onSlowRule)
|
|
1176
1218
|
return;
|
|
1177
|
-
|
|
1178
|
-
await hooks.onSlowRule(rule2, timeMs, threshold);
|
|
1179
|
-
} catch {}
|
|
1219
|
+
await executeWithTimeout("onSlowRule", () => hooks.onSlowRule(rule2, timeMs, threshold), hooks, timeoutMs);
|
|
1180
1220
|
};
|
|
1181
1221
|
|
|
1182
1222
|
// src/engine/state.ts
|
|
@@ -1361,24 +1401,25 @@ var cloneState = (state) => {
|
|
|
1361
1401
|
var resolveConfig = (config) => {
|
|
1362
1402
|
return {
|
|
1363
1403
|
consequences: config.consequences,
|
|
1364
|
-
conflictResolution: config.conflictResolution ??
|
|
1404
|
+
conflictResolution: config.conflictResolution ?? getDefaultConflictResolution(),
|
|
1365
1405
|
cache: {
|
|
1366
|
-
...
|
|
1406
|
+
...getDefaultCacheConfig(),
|
|
1367
1407
|
...config.cache
|
|
1368
1408
|
},
|
|
1369
1409
|
validation: {
|
|
1370
|
-
...
|
|
1410
|
+
...getDefaultValidationConfig(),
|
|
1371
1411
|
...config.validation
|
|
1372
1412
|
},
|
|
1373
1413
|
versioning: {
|
|
1374
|
-
...
|
|
1414
|
+
...getDefaultVersioningConfig(),
|
|
1375
1415
|
...config.versioning
|
|
1376
1416
|
},
|
|
1377
1417
|
hooks: config.hooks ?? {},
|
|
1378
|
-
logLevel: config.logLevel ??
|
|
1418
|
+
logLevel: config.logLevel ?? getDefaultLogLevel(),
|
|
1379
1419
|
logger: config.logger ?? console,
|
|
1380
|
-
continueOnError: config.continueOnError ??
|
|
1381
|
-
slowRuleThresholdMs: config.slowRuleThresholdMs ??
|
|
1420
|
+
continueOnError: config.continueOnError ?? true,
|
|
1421
|
+
slowRuleThresholdMs: config.slowRuleThresholdMs ?? 10,
|
|
1422
|
+
hookTimeoutMs: config.hookTimeoutMs
|
|
1382
1423
|
};
|
|
1383
1424
|
};
|
|
1384
1425
|
var createEngine = (config = {}) => {
|
|
@@ -1410,12 +1451,6 @@ var createEngine = (config = {}) => {
|
|
|
1410
1451
|
const ruleSetIds = new Set(ruleSetRules.map((r) => r.id));
|
|
1411
1452
|
rulesToEvaluate = rulesToEvaluate.filter((r) => ruleSetIds.has(r.id));
|
|
1412
1453
|
}
|
|
1413
|
-
rulesToEvaluate = [
|
|
1414
|
-
...sortRules({
|
|
1415
|
-
field: "priority",
|
|
1416
|
-
direction: "desc"
|
|
1417
|
-
})(rulesToEvaluate)
|
|
1418
|
-
];
|
|
1419
1454
|
if (options.maxRules && options.maxRules > 0) {
|
|
1420
1455
|
rulesToEvaluate = rulesToEvaluate.slice(0, options.maxRules);
|
|
1421
1456
|
}
|
|
@@ -1424,15 +1459,15 @@ var createEngine = (config = {}) => {
|
|
|
1424
1459
|
const cached = cache.get(cacheKey);
|
|
1425
1460
|
if (cached) {
|
|
1426
1461
|
cacheHits++;
|
|
1427
|
-
await executeOnCacheHit(resolvedConfig.hooks, cacheKey, cached);
|
|
1462
|
+
await executeOnCacheHit(resolvedConfig.hooks, cacheKey, cached, resolvedConfig.hookTimeoutMs);
|
|
1428
1463
|
return { ...cached, cacheHit: true };
|
|
1429
1464
|
}
|
|
1430
1465
|
}
|
|
1431
1466
|
if (cacheKey) {
|
|
1432
1467
|
cacheMisses++;
|
|
1433
|
-
await executeOnCacheMiss(resolvedConfig.hooks, cacheKey);
|
|
1468
|
+
await executeOnCacheMiss(resolvedConfig.hooks, cacheKey, resolvedConfig.hookTimeoutMs);
|
|
1434
1469
|
}
|
|
1435
|
-
await executeBeforeEvaluation(resolvedConfig.hooks, context, rulesToEvaluate);
|
|
1470
|
+
await executeBeforeEvaluation(resolvedConfig.hooks, context, rulesToEvaluate, resolvedConfig.hookTimeoutMs);
|
|
1436
1471
|
const { result: evaluationResult, durationMs } = measureTime(() => {
|
|
1437
1472
|
const results = [];
|
|
1438
1473
|
for (const rule2 of rulesToEvaluate) {
|
|
@@ -1449,29 +1484,29 @@ var createEngine = (config = {}) => {
|
|
|
1449
1484
|
let rulesErrored = 0;
|
|
1450
1485
|
const conflictResolution = options.conflictResolution ?? resolvedConfig.conflictResolution;
|
|
1451
1486
|
for (const { rule: rule2, result } of evaluationResult) {
|
|
1452
|
-
await executeBeforeRuleEvaluation(resolvedConfig.hooks, rule2, context);
|
|
1487
|
+
await executeBeforeRuleEvaluation(resolvedConfig.hooks, rule2, context, resolvedConfig.hookTimeoutMs);
|
|
1453
1488
|
ruleResults.push(result);
|
|
1454
1489
|
if (result.error) {
|
|
1455
1490
|
rulesErrored++;
|
|
1456
|
-
await executeOnRuleError(resolvedConfig.hooks, rule2, result.error);
|
|
1491
|
+
await executeOnRuleError(resolvedConfig.hooks, rule2, result.error, resolvedConfig.hookTimeoutMs);
|
|
1457
1492
|
if (!resolvedConfig.continueOnError) {
|
|
1458
1493
|
break;
|
|
1459
1494
|
}
|
|
1460
1495
|
}
|
|
1461
1496
|
if (result.skipped) {
|
|
1462
|
-
await executeOnRuleSkip(resolvedConfig.hooks, rule2, result.skipReason ?? "Unknown");
|
|
1497
|
+
await executeOnRuleSkip(resolvedConfig.hooks, rule2, result.skipReason ?? "Unknown", resolvedConfig.hookTimeoutMs);
|
|
1463
1498
|
}
|
|
1464
1499
|
if (result.evaluationTimeMs > resolvedConfig.slowRuleThresholdMs) {
|
|
1465
|
-
await executeOnSlowRule(resolvedConfig.hooks, rule2, result.evaluationTimeMs, resolvedConfig.slowRuleThresholdMs);
|
|
1500
|
+
await executeOnSlowRule(resolvedConfig.hooks, rule2, result.evaluationTimeMs, resolvedConfig.slowRuleThresholdMs, resolvedConfig.hookTimeoutMs);
|
|
1466
1501
|
}
|
|
1467
|
-
await executeAfterRuleEvaluation(resolvedConfig.hooks, rule2, result);
|
|
1502
|
+
await executeAfterRuleEvaluation(resolvedConfig.hooks, rule2, result, resolvedConfig.hookTimeoutMs);
|
|
1468
1503
|
if (result.matched) {
|
|
1469
1504
|
matchedRules.push(rule2);
|
|
1470
1505
|
for (const consequence of result.consequences) {
|
|
1471
1506
|
consequences.push(consequence);
|
|
1472
|
-
await executeOnConsequenceCollected(resolvedConfig.hooks, rule2, consequence);
|
|
1507
|
+
await executeOnConsequenceCollected(resolvedConfig.hooks, rule2, consequence, resolvedConfig.hookTimeoutMs);
|
|
1473
1508
|
}
|
|
1474
|
-
await executeOnRuleMatch(resolvedConfig.hooks, rule2, context);
|
|
1509
|
+
await executeOnRuleMatch(resolvedConfig.hooks, rule2, context, resolvedConfig.hookTimeoutMs);
|
|
1475
1510
|
if (rule2.stopOnMatch) {
|
|
1476
1511
|
stoppedEarly = true;
|
|
1477
1512
|
stoppedByRuleId = rule2.id;
|
|
@@ -1505,7 +1540,7 @@ var createEngine = (config = {}) => {
|
|
|
1505
1540
|
if (cacheKey) {
|
|
1506
1541
|
cache.set(cacheKey, executionResult);
|
|
1507
1542
|
}
|
|
1508
|
-
await executeAfterEvaluation(resolvedConfig.hooks, executionResult);
|
|
1543
|
+
await executeAfterEvaluation(resolvedConfig.hooks, executionResult, resolvedConfig.hookTimeoutMs);
|
|
1509
1544
|
return executionResult;
|
|
1510
1545
|
};
|
|
1511
1546
|
return {
|
|
@@ -1555,7 +1590,6 @@ var createEngine = (config = {}) => {
|
|
|
1555
1590
|
}),
|
|
1556
1591
|
getStats: () => {
|
|
1557
1592
|
const enabledRules = Array.from(state.rules.values()).filter((r) => r.enabled).length;
|
|
1558
|
-
const cacheStats = cache.getStats();
|
|
1559
1593
|
return {
|
|
1560
1594
|
totalRules: state.rules.size,
|
|
1561
1595
|
enabledRules,
|
|
@@ -1573,27 +1607,12 @@ var createEngine = (config = {}) => {
|
|
|
1573
1607
|
};
|
|
1574
1608
|
};
|
|
1575
1609
|
// src/optimizer/index-builder.ts
|
|
1576
|
-
var import_condition_evaluator3 = require("@f-o-t/condition-evaluator");
|
|
1577
1610
|
var DEFAULT_OPTIONS = {
|
|
1578
1611
|
indexByField: true,
|
|
1579
1612
|
indexByTag: true,
|
|
1580
1613
|
indexByCategory: true,
|
|
1581
1614
|
indexByPriority: true
|
|
1582
1615
|
};
|
|
1583
|
-
var collectFields2 = (condition) => {
|
|
1584
|
-
const fields = new Set;
|
|
1585
|
-
const traverse = (c) => {
|
|
1586
|
-
if (import_condition_evaluator3.isConditionGroup(c)) {
|
|
1587
|
-
for (const child of c.conditions) {
|
|
1588
|
-
traverse(child);
|
|
1589
|
-
}
|
|
1590
|
-
} else {
|
|
1591
|
-
fields.add(c.field);
|
|
1592
|
-
}
|
|
1593
|
-
};
|
|
1594
|
-
traverse(condition);
|
|
1595
|
-
return fields;
|
|
1596
|
-
};
|
|
1597
1616
|
var buildIndex = (rules, options = {}) => {
|
|
1598
1617
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
1599
1618
|
const byId = new Map;
|
|
@@ -1604,7 +1623,7 @@ var buildIndex = (rules, options = {}) => {
|
|
|
1604
1623
|
for (const rule2 of rules) {
|
|
1605
1624
|
byId.set(rule2.id, rule2);
|
|
1606
1625
|
if (opts.indexByField) {
|
|
1607
|
-
const fields =
|
|
1626
|
+
const fields = collectConditionFields(rule2.conditions);
|
|
1608
1627
|
for (const field of fields) {
|
|
1609
1628
|
const existing = byField.get(field) ?? [];
|
|
1610
1629
|
existing.push(rule2);
|
|
@@ -1709,7 +1728,7 @@ var analyzeOptimizations = (rules) => {
|
|
|
1709
1728
|
const suggestions = [];
|
|
1710
1729
|
const fieldUsage = new Map;
|
|
1711
1730
|
for (const rule2 of rules) {
|
|
1712
|
-
const fields =
|
|
1731
|
+
const fields = collectConditionFields(rule2.conditions);
|
|
1713
1732
|
for (const field of fields) {
|
|
1714
1733
|
fieldUsage.set(field, (fieldUsage.get(field) ?? 0) + 1);
|
|
1715
1734
|
}
|
|
@@ -1862,6 +1881,8 @@ var importRules = (data, options = {}) => {
|
|
|
1862
1881
|
for (let i = 0;i < data.rules.length; i++) {
|
|
1863
1882
|
try {
|
|
1864
1883
|
const serialized = data.rules[i];
|
|
1884
|
+
if (!serialized)
|
|
1885
|
+
continue;
|
|
1865
1886
|
const rule2 = deserializeRule(serialized, options);
|
|
1866
1887
|
idMapping.set(serialized.id, rule2.id);
|
|
1867
1888
|
rules.push(rule2);
|
|
@@ -1877,6 +1898,8 @@ var importRules = (data, options = {}) => {
|
|
|
1877
1898
|
for (let i = 0;i < data.ruleSets.length; i++) {
|
|
1878
1899
|
try {
|
|
1879
1900
|
const serialized = data.ruleSets[i];
|
|
1901
|
+
if (!serialized)
|
|
1902
|
+
continue;
|
|
1880
1903
|
const ruleSet = deserializeRuleSet(serialized, idMapping, options);
|
|
1881
1904
|
ruleSets.push(ruleSet);
|
|
1882
1905
|
} catch (error) {
|
|
@@ -1888,12 +1911,25 @@ var importRules = (data, options = {}) => {
|
|
|
1888
1911
|
}
|
|
1889
1912
|
}
|
|
1890
1913
|
}
|
|
1914
|
+
const importedRuleIds = new Set(rules.map((r) => r.id));
|
|
1915
|
+
const orphanedReferences = [];
|
|
1916
|
+
for (const ruleSet of ruleSets) {
|
|
1917
|
+
const missingRuleIds = ruleSet.ruleIds.filter((id) => !importedRuleIds.has(id));
|
|
1918
|
+
if (missingRuleIds.length > 0) {
|
|
1919
|
+
orphanedReferences.push({
|
|
1920
|
+
ruleSetId: ruleSet.id,
|
|
1921
|
+
ruleSetName: ruleSet.name,
|
|
1922
|
+
missingRuleIds
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1891
1926
|
return {
|
|
1892
1927
|
success: errors.length === 0,
|
|
1893
1928
|
rules,
|
|
1894
1929
|
ruleSets,
|
|
1895
1930
|
errors,
|
|
1896
|
-
idMapping
|
|
1931
|
+
idMapping,
|
|
1932
|
+
orphanedReferences
|
|
1897
1933
|
};
|
|
1898
1934
|
};
|
|
1899
1935
|
var importFromJson = (json, options = {}) => {
|
|
@@ -1912,7 +1948,8 @@ var importFromJson = (json, options = {}) => {
|
|
|
1912
1948
|
message: `Invalid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
1913
1949
|
}
|
|
1914
1950
|
],
|
|
1915
|
-
idMapping: new Map
|
|
1951
|
+
idMapping: new Map,
|
|
1952
|
+
orphanedReferences: []
|
|
1916
1953
|
};
|
|
1917
1954
|
}
|
|
1918
1955
|
};
|
|
@@ -2049,8 +2086,8 @@ var whatIf = (originalRules, modifiedRules, context) => {
|
|
|
2049
2086
|
const [ruleId, consequenceType] = key.split(":");
|
|
2050
2087
|
consequenceChanges.push({
|
|
2051
2088
|
type: "added",
|
|
2052
|
-
consequenceType,
|
|
2053
|
-
ruleId
|
|
2089
|
+
consequenceType: consequenceType ?? "",
|
|
2090
|
+
ruleId: ruleId ?? ""
|
|
2054
2091
|
});
|
|
2055
2092
|
}
|
|
2056
2093
|
}
|
|
@@ -2059,8 +2096,8 @@ var whatIf = (originalRules, modifiedRules, context) => {
|
|
|
2059
2096
|
const [ruleId, consequenceType] = key.split(":");
|
|
2060
2097
|
consequenceChanges.push({
|
|
2061
2098
|
type: "removed",
|
|
2062
|
-
consequenceType,
|
|
2063
|
-
ruleId
|
|
2099
|
+
consequenceType: consequenceType ?? "",
|
|
2100
|
+
ruleId: ruleId ?? ""
|
|
2064
2101
|
});
|
|
2065
2102
|
}
|
|
2066
2103
|
}
|
|
@@ -2149,49 +2186,59 @@ var formatSimulationResult = (result) => {
|
|
|
2149
2186
|
return lines.join(`
|
|
2150
2187
|
`);
|
|
2151
2188
|
};
|
|
2189
|
+
// src/types/evaluation.ts
|
|
2190
|
+
var import_zod3 = require("zod");
|
|
2191
|
+
var ConflictResolutionStrategySchema = import_zod3.z.enum([
|
|
2192
|
+
"priority",
|
|
2193
|
+
"first-match",
|
|
2194
|
+
"all",
|
|
2195
|
+
"most-specific"
|
|
2196
|
+
]);
|
|
2197
|
+
var EvaluateOptionsSchema = import_zod3.z.object({
|
|
2198
|
+
conflictResolution: ConflictResolutionStrategySchema.optional(),
|
|
2199
|
+
maxRules: import_zod3.z.number().int().positive().optional(),
|
|
2200
|
+
timeout: import_zod3.z.number().int().positive().optional(),
|
|
2201
|
+
skipDisabled: import_zod3.z.boolean().optional(),
|
|
2202
|
+
tags: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
2203
|
+
category: import_zod3.z.string().optional(),
|
|
2204
|
+
ruleSetId: import_zod3.z.string().optional(),
|
|
2205
|
+
bypassCache: import_zod3.z.boolean().optional()
|
|
2206
|
+
});
|
|
2207
|
+
var EvaluateConfigSchema = import_zod3.z.object({
|
|
2208
|
+
conflictResolution: ConflictResolutionStrategySchema,
|
|
2209
|
+
continueOnError: import_zod3.z.boolean(),
|
|
2210
|
+
collectAllConsequences: import_zod3.z.boolean()
|
|
2211
|
+
});
|
|
2152
2212
|
// src/types/rule.ts
|
|
2153
|
-
var
|
|
2154
|
-
var RuleSchema =
|
|
2155
|
-
id:
|
|
2156
|
-
name:
|
|
2157
|
-
description:
|
|
2158
|
-
conditions:
|
|
2213
|
+
var import_zod4 = require("zod");
|
|
2214
|
+
var RuleSchema = import_zod4.z.object({
|
|
2215
|
+
id: import_zod4.z.string().min(1),
|
|
2216
|
+
name: import_zod4.z.string().min(1),
|
|
2217
|
+
description: import_zod4.z.string().optional(),
|
|
2218
|
+
conditions: import_zod4.z.custom((val) => {
|
|
2159
2219
|
return typeof val === "object" && val !== null && "id" in val && "operator" in val;
|
|
2160
2220
|
}, "Invalid condition group"),
|
|
2161
|
-
consequences:
|
|
2162
|
-
type:
|
|
2163
|
-
payload:
|
|
2221
|
+
consequences: import_zod4.z.array(import_zod4.z.object({
|
|
2222
|
+
type: import_zod4.z.string(),
|
|
2223
|
+
payload: import_zod4.z.unknown()
|
|
2164
2224
|
})),
|
|
2165
|
-
priority:
|
|
2166
|
-
enabled:
|
|
2167
|
-
stopOnMatch:
|
|
2168
|
-
tags:
|
|
2169
|
-
category:
|
|
2170
|
-
metadata:
|
|
2171
|
-
createdAt:
|
|
2172
|
-
updatedAt:
|
|
2225
|
+
priority: import_zod4.z.number().int().default(0),
|
|
2226
|
+
enabled: import_zod4.z.boolean().default(true),
|
|
2227
|
+
stopOnMatch: import_zod4.z.boolean().default(false),
|
|
2228
|
+
tags: import_zod4.z.array(import_zod4.z.string()).default([]),
|
|
2229
|
+
category: import_zod4.z.string().optional(),
|
|
2230
|
+
metadata: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional(),
|
|
2231
|
+
createdAt: import_zod4.z.date().default(() => new Date),
|
|
2232
|
+
updatedAt: import_zod4.z.date().default(() => new Date)
|
|
2173
2233
|
});
|
|
2174
|
-
var RuleSetSchema =
|
|
2175
|
-
id:
|
|
2176
|
-
name:
|
|
2177
|
-
description:
|
|
2178
|
-
ruleIds:
|
|
2179
|
-
enabled:
|
|
2180
|
-
metadata:
|
|
2234
|
+
var RuleSetSchema = import_zod4.z.object({
|
|
2235
|
+
id: import_zod4.z.string().min(1),
|
|
2236
|
+
name: import_zod4.z.string().min(1),
|
|
2237
|
+
description: import_zod4.z.string().optional(),
|
|
2238
|
+
ruleIds: import_zod4.z.array(import_zod4.z.string()),
|
|
2239
|
+
enabled: import_zod4.z.boolean().default(true),
|
|
2240
|
+
metadata: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional()
|
|
2181
2241
|
});
|
|
2182
|
-
// src/utils/pipe.ts
|
|
2183
|
-
function pipe(...fns) {
|
|
2184
|
-
return (value) => fns.reduce((acc, fn) => fn(acc), value);
|
|
2185
|
-
}
|
|
2186
|
-
function compose(...fns) {
|
|
2187
|
-
return (value) => fns.reduceRight((acc, fn) => fn(acc), value);
|
|
2188
|
-
}
|
|
2189
|
-
var identity = (value) => value;
|
|
2190
|
-
var always = (value) => () => value;
|
|
2191
|
-
var tap = (fn) => (value) => {
|
|
2192
|
-
fn(value);
|
|
2193
|
-
return value;
|
|
2194
|
-
};
|
|
2195
2242
|
// src/validation/conflicts.ts
|
|
2196
2243
|
var import_condition_evaluator4 = require("@f-o-t/condition-evaluator");
|
|
2197
2244
|
var DEFAULT_OPTIONS2 = {
|
|
@@ -2201,11 +2248,11 @@ var DEFAULT_OPTIONS2 = {
|
|
|
2201
2248
|
checkPriorityCollisions: true,
|
|
2202
2249
|
checkUnreachableRules: true
|
|
2203
2250
|
};
|
|
2204
|
-
var
|
|
2251
|
+
var collectConditionFields2 = (condition) => {
|
|
2205
2252
|
const fields = new Set;
|
|
2206
2253
|
if (import_condition_evaluator4.isConditionGroup(condition)) {
|
|
2207
2254
|
for (const child of condition.conditions) {
|
|
2208
|
-
const childFields =
|
|
2255
|
+
const childFields = collectConditionFields2(child);
|
|
2209
2256
|
for (const field of childFields) {
|
|
2210
2257
|
fields.add(field);
|
|
2211
2258
|
}
|
|
@@ -2240,8 +2287,8 @@ var getConditionOperatorValues = (condition, field) => {
|
|
|
2240
2287
|
return results;
|
|
2241
2288
|
};
|
|
2242
2289
|
var areConditionsOverlapping = (conditions1, conditions2) => {
|
|
2243
|
-
const fields1 =
|
|
2244
|
-
const fields2 =
|
|
2290
|
+
const fields1 = collectConditionFields2(conditions1);
|
|
2291
|
+
const fields2 = collectConditionFields2(conditions2);
|
|
2245
2292
|
const commonFields = new Set([...fields1].filter((f) => fields2.has(f)));
|
|
2246
2293
|
if (commonFields.size === 0)
|
|
2247
2294
|
return false;
|
|
@@ -2324,6 +2371,8 @@ var findOverlappingConditions = (rules) => {
|
|
|
2324
2371
|
for (let j = i + 1;j < rules.length; j++) {
|
|
2325
2372
|
const rule1 = rules[i];
|
|
2326
2373
|
const rule2 = rules[j];
|
|
2374
|
+
if (!rule1 || !rule2)
|
|
2375
|
+
continue;
|
|
2327
2376
|
const key = [rule1.id, rule2.id].sort().join(":");
|
|
2328
2377
|
if (checked.has(key))
|
|
2329
2378
|
continue;
|
|
@@ -2336,8 +2385,8 @@ var findOverlappingConditions = (rules) => {
|
|
|
2336
2385
|
ruleIds: [rule1.id, rule2.id],
|
|
2337
2386
|
rules: [rule1, rule2],
|
|
2338
2387
|
details: {
|
|
2339
|
-
rule1Fields: [...
|
|
2340
|
-
rule2Fields: [...
|
|
2388
|
+
rule1Fields: [...collectConditionFields2(rule1.conditions)],
|
|
2389
|
+
rule2Fields: [...collectConditionFields2(rule2.conditions)]
|
|
2341
2390
|
}
|
|
2342
2391
|
});
|
|
2343
2392
|
}
|
|
@@ -2360,6 +2409,8 @@ var findPriorityCollisions = (rules) => {
|
|
|
2360
2409
|
for (let j = i + 1;j < rulesWithPriority.length; j++) {
|
|
2361
2410
|
const r1 = rulesWithPriority[i];
|
|
2362
2411
|
const r2 = rulesWithPriority[j];
|
|
2412
|
+
if (!r1 || !r2)
|
|
2413
|
+
continue;
|
|
2363
2414
|
if (areConditionsOverlapping(r1.conditions, r2.conditions)) {
|
|
2364
2415
|
overlappingPairs.add(r1.id);
|
|
2365
2416
|
overlappingPairs.add(r2.id);
|
|
@@ -2386,10 +2437,14 @@ var findUnreachableRules = (rules) => {
|
|
|
2386
2437
|
const sortedRules = [...rules].sort((a, b) => b.priority - a.priority);
|
|
2387
2438
|
for (let i = 0;i < sortedRules.length; i++) {
|
|
2388
2439
|
const rule2 = sortedRules[i];
|
|
2440
|
+
if (!rule2)
|
|
2441
|
+
continue;
|
|
2389
2442
|
if (!rule2.enabled)
|
|
2390
2443
|
continue;
|
|
2391
2444
|
for (let j = 0;j < i; j++) {
|
|
2392
2445
|
const higherPriorityRule = sortedRules[j];
|
|
2446
|
+
if (!higherPriorityRule)
|
|
2447
|
+
continue;
|
|
2393
2448
|
if (!higherPriorityRule.enabled || !higherPriorityRule.stopOnMatch) {
|
|
2394
2449
|
continue;
|
|
2395
2450
|
}
|
|
@@ -2528,7 +2583,7 @@ var checkRuleIntegrity = (rule2, options) => {
|
|
|
2528
2583
|
}
|
|
2529
2584
|
}
|
|
2530
2585
|
if (options.allowedTags) {
|
|
2531
|
-
const invalidTags = rule2.tags.filter((t) => !options.allowedTags
|
|
2586
|
+
const invalidTags = rule2.tags.filter((t) => !options.allowedTags?.includes(t));
|
|
2532
2587
|
if (invalidTags.length > 0) {
|
|
2533
2588
|
issues.push(createIssue("INVALID_TAGS", `Rule "${rule2.name}" has invalid tags: ${invalidTags.join(", ")}`, "warning", {
|
|
2534
2589
|
ruleId: rule2.id,
|
|
@@ -2701,11 +2756,22 @@ ${severity.toUpperCase()}S (${issues.length}):`);
|
|
|
2701
2756
|
};
|
|
2702
2757
|
// src/validation/schema.ts
|
|
2703
2758
|
var import_condition_evaluator6 = require("@f-o-t/condition-evaluator");
|
|
2704
|
-
var
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2759
|
+
var import_zod5 = require("zod");
|
|
2760
|
+
var ValidationErrorSchema = import_zod5.z.object({
|
|
2761
|
+
path: import_zod5.z.string(),
|
|
2762
|
+
message: import_zod5.z.string(),
|
|
2763
|
+
code: import_zod5.z.string()
|
|
2764
|
+
});
|
|
2765
|
+
var ValidationResultSchema = import_zod5.z.object({
|
|
2766
|
+
valid: import_zod5.z.boolean(),
|
|
2767
|
+
errors: import_zod5.z.array(ValidationErrorSchema)
|
|
2768
|
+
});
|
|
2769
|
+
var ValidationOptionsSchema = import_zod5.z.object({
|
|
2770
|
+
validateConditions: import_zod5.z.boolean().optional(),
|
|
2771
|
+
validateConsequences: import_zod5.z.boolean().optional(),
|
|
2772
|
+
strictMode: import_zod5.z.boolean().optional()
|
|
2773
|
+
});
|
|
2774
|
+
var DEFAULT_OPTIONS4 = ValidationOptionsSchema.parse({});
|
|
2709
2775
|
var createError = (path, message, code) => ({
|
|
2710
2776
|
path,
|
|
2711
2777
|
message,
|
|
@@ -2717,7 +2783,7 @@ var validResult = () => ({
|
|
|
2717
2783
|
});
|
|
2718
2784
|
var invalidResult = (errors) => ({
|
|
2719
2785
|
valid: false,
|
|
2720
|
-
errors
|
|
2786
|
+
errors: [...errors]
|
|
2721
2787
|
});
|
|
2722
2788
|
var validateConditionStructure = (condition, path) => {
|
|
2723
2789
|
const errors = [];
|
|
@@ -2758,6 +2824,8 @@ var validateConsequenceStructure = (consequences, consequenceSchemas, strictMode
|
|
|
2758
2824
|
const errors = [];
|
|
2759
2825
|
for (let i = 0;i < consequences.length; i++) {
|
|
2760
2826
|
const consequence = consequences[i];
|
|
2827
|
+
if (!consequence)
|
|
2828
|
+
continue;
|
|
2761
2829
|
const path = `consequences[${i}]`;
|
|
2762
2830
|
if (!consequence.type || typeof consequence.type !== "string") {
|
|
2763
2831
|
errors.push(createError(`${path}.type`, "Consequence must have a type", "MISSING_CONSEQUENCE_TYPE"));
|
|
@@ -2794,8 +2862,8 @@ var validateRule = (rule2, options = {}) => {
|
|
|
2794
2862
|
const conditionErrors = validateConditionStructure(validRule.conditions, "conditions");
|
|
2795
2863
|
errors.push(...conditionErrors);
|
|
2796
2864
|
}
|
|
2797
|
-
if (opts.validateConsequences) {
|
|
2798
|
-
const consequenceErrors = validateConsequenceStructure(validRule.consequences, opts.consequenceSchemas, opts.strictMode);
|
|
2865
|
+
if (opts.validateConsequences || opts.strictMode) {
|
|
2866
|
+
const consequenceErrors = validateConsequenceStructure(validRule.consequences, opts.consequenceSchemas, opts.strictMode ?? false);
|
|
2799
2867
|
errors.push(...consequenceErrors);
|
|
2800
2868
|
}
|
|
2801
2869
|
return errors.length > 0 ? invalidResult(errors) : validResult();
|