@f-o-t/rules-engine 1.0.0 → 2.0.0
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 +287 -218
- package/dist/index.d.cts +191 -186
- package/dist/index.d.ts +191 -186
- package/dist/index.js +292 -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,7 @@ var createEngine = (config = {}) => {
|
|
|
1555
1590
|
}),
|
|
1556
1591
|
getStats: () => {
|
|
1557
1592
|
const enabledRules = Array.from(state.rules.values()).filter((r) => r.enabled).length;
|
|
1558
|
-
const
|
|
1593
|
+
const _cacheStats = cache.getStats();
|
|
1559
1594
|
return {
|
|
1560
1595
|
totalRules: state.rules.size,
|
|
1561
1596
|
enabledRules,
|
|
@@ -1573,27 +1608,12 @@ var createEngine = (config = {}) => {
|
|
|
1573
1608
|
};
|
|
1574
1609
|
};
|
|
1575
1610
|
// src/optimizer/index-builder.ts
|
|
1576
|
-
var import_condition_evaluator3 = require("@f-o-t/condition-evaluator");
|
|
1577
1611
|
var DEFAULT_OPTIONS = {
|
|
1578
1612
|
indexByField: true,
|
|
1579
1613
|
indexByTag: true,
|
|
1580
1614
|
indexByCategory: true,
|
|
1581
1615
|
indexByPriority: true
|
|
1582
1616
|
};
|
|
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
1617
|
var buildIndex = (rules, options = {}) => {
|
|
1598
1618
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
1599
1619
|
const byId = new Map;
|
|
@@ -1604,7 +1624,7 @@ var buildIndex = (rules, options = {}) => {
|
|
|
1604
1624
|
for (const rule2 of rules) {
|
|
1605
1625
|
byId.set(rule2.id, rule2);
|
|
1606
1626
|
if (opts.indexByField) {
|
|
1607
|
-
const fields =
|
|
1627
|
+
const fields = collectConditionFields(rule2.conditions);
|
|
1608
1628
|
for (const field of fields) {
|
|
1609
1629
|
const existing = byField.get(field) ?? [];
|
|
1610
1630
|
existing.push(rule2);
|
|
@@ -1709,7 +1729,7 @@ var analyzeOptimizations = (rules) => {
|
|
|
1709
1729
|
const suggestions = [];
|
|
1710
1730
|
const fieldUsage = new Map;
|
|
1711
1731
|
for (const rule2 of rules) {
|
|
1712
|
-
const fields =
|
|
1732
|
+
const fields = collectConditionFields(rule2.conditions);
|
|
1713
1733
|
for (const field of fields) {
|
|
1714
1734
|
fieldUsage.set(field, (fieldUsage.get(field) ?? 0) + 1);
|
|
1715
1735
|
}
|
|
@@ -1862,6 +1882,8 @@ var importRules = (data, options = {}) => {
|
|
|
1862
1882
|
for (let i = 0;i < data.rules.length; i++) {
|
|
1863
1883
|
try {
|
|
1864
1884
|
const serialized = data.rules[i];
|
|
1885
|
+
if (!serialized)
|
|
1886
|
+
continue;
|
|
1865
1887
|
const rule2 = deserializeRule(serialized, options);
|
|
1866
1888
|
idMapping.set(serialized.id, rule2.id);
|
|
1867
1889
|
rules.push(rule2);
|
|
@@ -1877,6 +1899,8 @@ var importRules = (data, options = {}) => {
|
|
|
1877
1899
|
for (let i = 0;i < data.ruleSets.length; i++) {
|
|
1878
1900
|
try {
|
|
1879
1901
|
const serialized = data.ruleSets[i];
|
|
1902
|
+
if (!serialized)
|
|
1903
|
+
continue;
|
|
1880
1904
|
const ruleSet = deserializeRuleSet(serialized, idMapping, options);
|
|
1881
1905
|
ruleSets.push(ruleSet);
|
|
1882
1906
|
} catch (error) {
|
|
@@ -1888,12 +1912,25 @@ var importRules = (data, options = {}) => {
|
|
|
1888
1912
|
}
|
|
1889
1913
|
}
|
|
1890
1914
|
}
|
|
1915
|
+
const importedRuleIds = new Set(rules.map((r) => r.id));
|
|
1916
|
+
const orphanedReferences = [];
|
|
1917
|
+
for (const ruleSet of ruleSets) {
|
|
1918
|
+
const missingRuleIds = ruleSet.ruleIds.filter((id) => !importedRuleIds.has(id));
|
|
1919
|
+
if (missingRuleIds.length > 0) {
|
|
1920
|
+
orphanedReferences.push({
|
|
1921
|
+
ruleSetId: ruleSet.id,
|
|
1922
|
+
ruleSetName: ruleSet.name,
|
|
1923
|
+
missingRuleIds
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1891
1927
|
return {
|
|
1892
1928
|
success: errors.length === 0,
|
|
1893
1929
|
rules,
|
|
1894
1930
|
ruleSets,
|
|
1895
1931
|
errors,
|
|
1896
|
-
idMapping
|
|
1932
|
+
idMapping,
|
|
1933
|
+
orphanedReferences
|
|
1897
1934
|
};
|
|
1898
1935
|
};
|
|
1899
1936
|
var importFromJson = (json, options = {}) => {
|
|
@@ -1912,7 +1949,8 @@ var importFromJson = (json, options = {}) => {
|
|
|
1912
1949
|
message: `Invalid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
1913
1950
|
}
|
|
1914
1951
|
],
|
|
1915
|
-
idMapping: new Map
|
|
1952
|
+
idMapping: new Map,
|
|
1953
|
+
orphanedReferences: []
|
|
1916
1954
|
};
|
|
1917
1955
|
}
|
|
1918
1956
|
};
|
|
@@ -2049,8 +2087,8 @@ var whatIf = (originalRules, modifiedRules, context) => {
|
|
|
2049
2087
|
const [ruleId, consequenceType] = key.split(":");
|
|
2050
2088
|
consequenceChanges.push({
|
|
2051
2089
|
type: "added",
|
|
2052
|
-
consequenceType,
|
|
2053
|
-
ruleId
|
|
2090
|
+
consequenceType: consequenceType ?? "",
|
|
2091
|
+
ruleId: ruleId ?? ""
|
|
2054
2092
|
});
|
|
2055
2093
|
}
|
|
2056
2094
|
}
|
|
@@ -2059,8 +2097,8 @@ var whatIf = (originalRules, modifiedRules, context) => {
|
|
|
2059
2097
|
const [ruleId, consequenceType] = key.split(":");
|
|
2060
2098
|
consequenceChanges.push({
|
|
2061
2099
|
type: "removed",
|
|
2062
|
-
consequenceType,
|
|
2063
|
-
ruleId
|
|
2100
|
+
consequenceType: consequenceType ?? "",
|
|
2101
|
+
ruleId: ruleId ?? ""
|
|
2064
2102
|
});
|
|
2065
2103
|
}
|
|
2066
2104
|
}
|
|
@@ -2149,49 +2187,59 @@ var formatSimulationResult = (result) => {
|
|
|
2149
2187
|
return lines.join(`
|
|
2150
2188
|
`);
|
|
2151
2189
|
};
|
|
2190
|
+
// src/types/evaluation.ts
|
|
2191
|
+
var import_zod3 = require("zod");
|
|
2192
|
+
var ConflictResolutionStrategySchema = import_zod3.z.enum([
|
|
2193
|
+
"priority",
|
|
2194
|
+
"first-match",
|
|
2195
|
+
"all",
|
|
2196
|
+
"most-specific"
|
|
2197
|
+
]);
|
|
2198
|
+
var EvaluateOptionsSchema = import_zod3.z.object({
|
|
2199
|
+
conflictResolution: ConflictResolutionStrategySchema.optional(),
|
|
2200
|
+
maxRules: import_zod3.z.number().int().positive().optional(),
|
|
2201
|
+
timeout: import_zod3.z.number().int().positive().optional(),
|
|
2202
|
+
skipDisabled: import_zod3.z.boolean().optional(),
|
|
2203
|
+
tags: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
2204
|
+
category: import_zod3.z.string().optional(),
|
|
2205
|
+
ruleSetId: import_zod3.z.string().optional(),
|
|
2206
|
+
bypassCache: import_zod3.z.boolean().optional()
|
|
2207
|
+
});
|
|
2208
|
+
var EvaluateConfigSchema = import_zod3.z.object({
|
|
2209
|
+
conflictResolution: ConflictResolutionStrategySchema,
|
|
2210
|
+
continueOnError: import_zod3.z.boolean(),
|
|
2211
|
+
collectAllConsequences: import_zod3.z.boolean()
|
|
2212
|
+
});
|
|
2152
2213
|
// src/types/rule.ts
|
|
2153
|
-
var
|
|
2154
|
-
var RuleSchema =
|
|
2155
|
-
id:
|
|
2156
|
-
name:
|
|
2157
|
-
description:
|
|
2158
|
-
conditions:
|
|
2214
|
+
var import_zod4 = require("zod");
|
|
2215
|
+
var RuleSchema = import_zod4.z.object({
|
|
2216
|
+
id: import_zod4.z.string().min(1),
|
|
2217
|
+
name: import_zod4.z.string().min(1),
|
|
2218
|
+
description: import_zod4.z.string().optional(),
|
|
2219
|
+
conditions: import_zod4.z.custom((val) => {
|
|
2159
2220
|
return typeof val === "object" && val !== null && "id" in val && "operator" in val;
|
|
2160
2221
|
}, "Invalid condition group"),
|
|
2161
|
-
consequences:
|
|
2162
|
-
type:
|
|
2163
|
-
payload:
|
|
2222
|
+
consequences: import_zod4.z.array(import_zod4.z.object({
|
|
2223
|
+
type: import_zod4.z.string(),
|
|
2224
|
+
payload: import_zod4.z.unknown()
|
|
2164
2225
|
})),
|
|
2165
|
-
priority:
|
|
2166
|
-
enabled:
|
|
2167
|
-
stopOnMatch:
|
|
2168
|
-
tags:
|
|
2169
|
-
category:
|
|
2170
|
-
metadata:
|
|
2171
|
-
createdAt:
|
|
2172
|
-
updatedAt:
|
|
2226
|
+
priority: import_zod4.z.number().int().default(0),
|
|
2227
|
+
enabled: import_zod4.z.boolean().default(true),
|
|
2228
|
+
stopOnMatch: import_zod4.z.boolean().default(false),
|
|
2229
|
+
tags: import_zod4.z.array(import_zod4.z.string()).default([]),
|
|
2230
|
+
category: import_zod4.z.string().optional(),
|
|
2231
|
+
metadata: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional(),
|
|
2232
|
+
createdAt: import_zod4.z.date().default(() => new Date),
|
|
2233
|
+
updatedAt: import_zod4.z.date().default(() => new Date)
|
|
2173
2234
|
});
|
|
2174
|
-
var RuleSetSchema =
|
|
2175
|
-
id:
|
|
2176
|
-
name:
|
|
2177
|
-
description:
|
|
2178
|
-
ruleIds:
|
|
2179
|
-
enabled:
|
|
2180
|
-
metadata:
|
|
2235
|
+
var RuleSetSchema = import_zod4.z.object({
|
|
2236
|
+
id: import_zod4.z.string().min(1),
|
|
2237
|
+
name: import_zod4.z.string().min(1),
|
|
2238
|
+
description: import_zod4.z.string().optional(),
|
|
2239
|
+
ruleIds: import_zod4.z.array(import_zod4.z.string()),
|
|
2240
|
+
enabled: import_zod4.z.boolean().default(true),
|
|
2241
|
+
metadata: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional()
|
|
2181
2242
|
});
|
|
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
2243
|
// src/validation/conflicts.ts
|
|
2196
2244
|
var import_condition_evaluator4 = require("@f-o-t/condition-evaluator");
|
|
2197
2245
|
var DEFAULT_OPTIONS2 = {
|
|
@@ -2201,11 +2249,11 @@ var DEFAULT_OPTIONS2 = {
|
|
|
2201
2249
|
checkPriorityCollisions: true,
|
|
2202
2250
|
checkUnreachableRules: true
|
|
2203
2251
|
};
|
|
2204
|
-
var
|
|
2252
|
+
var collectConditionFields2 = (condition) => {
|
|
2205
2253
|
const fields = new Set;
|
|
2206
2254
|
if (import_condition_evaluator4.isConditionGroup(condition)) {
|
|
2207
2255
|
for (const child of condition.conditions) {
|
|
2208
|
-
const childFields =
|
|
2256
|
+
const childFields = collectConditionFields2(child);
|
|
2209
2257
|
for (const field of childFields) {
|
|
2210
2258
|
fields.add(field);
|
|
2211
2259
|
}
|
|
@@ -2240,8 +2288,8 @@ var getConditionOperatorValues = (condition, field) => {
|
|
|
2240
2288
|
return results;
|
|
2241
2289
|
};
|
|
2242
2290
|
var areConditionsOverlapping = (conditions1, conditions2) => {
|
|
2243
|
-
const fields1 =
|
|
2244
|
-
const fields2 =
|
|
2291
|
+
const fields1 = collectConditionFields2(conditions1);
|
|
2292
|
+
const fields2 = collectConditionFields2(conditions2);
|
|
2245
2293
|
const commonFields = new Set([...fields1].filter((f) => fields2.has(f)));
|
|
2246
2294
|
if (commonFields.size === 0)
|
|
2247
2295
|
return false;
|
|
@@ -2324,6 +2372,8 @@ var findOverlappingConditions = (rules) => {
|
|
|
2324
2372
|
for (let j = i + 1;j < rules.length; j++) {
|
|
2325
2373
|
const rule1 = rules[i];
|
|
2326
2374
|
const rule2 = rules[j];
|
|
2375
|
+
if (!rule1 || !rule2)
|
|
2376
|
+
continue;
|
|
2327
2377
|
const key = [rule1.id, rule2.id].sort().join(":");
|
|
2328
2378
|
if (checked.has(key))
|
|
2329
2379
|
continue;
|
|
@@ -2336,8 +2386,8 @@ var findOverlappingConditions = (rules) => {
|
|
|
2336
2386
|
ruleIds: [rule1.id, rule2.id],
|
|
2337
2387
|
rules: [rule1, rule2],
|
|
2338
2388
|
details: {
|
|
2339
|
-
rule1Fields: [...
|
|
2340
|
-
rule2Fields: [...
|
|
2389
|
+
rule1Fields: [...collectConditionFields2(rule1.conditions)],
|
|
2390
|
+
rule2Fields: [...collectConditionFields2(rule2.conditions)]
|
|
2341
2391
|
}
|
|
2342
2392
|
});
|
|
2343
2393
|
}
|
|
@@ -2360,6 +2410,8 @@ var findPriorityCollisions = (rules) => {
|
|
|
2360
2410
|
for (let j = i + 1;j < rulesWithPriority.length; j++) {
|
|
2361
2411
|
const r1 = rulesWithPriority[i];
|
|
2362
2412
|
const r2 = rulesWithPriority[j];
|
|
2413
|
+
if (!r1 || !r2)
|
|
2414
|
+
continue;
|
|
2363
2415
|
if (areConditionsOverlapping(r1.conditions, r2.conditions)) {
|
|
2364
2416
|
overlappingPairs.add(r1.id);
|
|
2365
2417
|
overlappingPairs.add(r2.id);
|
|
@@ -2386,10 +2438,14 @@ var findUnreachableRules = (rules) => {
|
|
|
2386
2438
|
const sortedRules = [...rules].sort((a, b) => b.priority - a.priority);
|
|
2387
2439
|
for (let i = 0;i < sortedRules.length; i++) {
|
|
2388
2440
|
const rule2 = sortedRules[i];
|
|
2441
|
+
if (!rule2)
|
|
2442
|
+
continue;
|
|
2389
2443
|
if (!rule2.enabled)
|
|
2390
2444
|
continue;
|
|
2391
2445
|
for (let j = 0;j < i; j++) {
|
|
2392
2446
|
const higherPriorityRule = sortedRules[j];
|
|
2447
|
+
if (!higherPriorityRule)
|
|
2448
|
+
continue;
|
|
2393
2449
|
if (!higherPriorityRule.enabled || !higherPriorityRule.stopOnMatch) {
|
|
2394
2450
|
continue;
|
|
2395
2451
|
}
|
|
@@ -2528,7 +2584,7 @@ var checkRuleIntegrity = (rule2, options) => {
|
|
|
2528
2584
|
}
|
|
2529
2585
|
}
|
|
2530
2586
|
if (options.allowedTags) {
|
|
2531
|
-
const invalidTags = rule2.tags.filter((t) => !options.allowedTags
|
|
2587
|
+
const invalidTags = rule2.tags.filter((t) => !options.allowedTags?.includes(t));
|
|
2532
2588
|
if (invalidTags.length > 0) {
|
|
2533
2589
|
issues.push(createIssue("INVALID_TAGS", `Rule "${rule2.name}" has invalid tags: ${invalidTags.join(", ")}`, "warning", {
|
|
2534
2590
|
ruleId: rule2.id,
|
|
@@ -2701,11 +2757,22 @@ ${severity.toUpperCase()}S (${issues.length}):`);
|
|
|
2701
2757
|
};
|
|
2702
2758
|
// src/validation/schema.ts
|
|
2703
2759
|
var import_condition_evaluator6 = require("@f-o-t/condition-evaluator");
|
|
2704
|
-
var
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2760
|
+
var import_zod5 = require("zod");
|
|
2761
|
+
var ValidationErrorSchema = import_zod5.z.object({
|
|
2762
|
+
path: import_zod5.z.string(),
|
|
2763
|
+
message: import_zod5.z.string(),
|
|
2764
|
+
code: import_zod5.z.string()
|
|
2765
|
+
});
|
|
2766
|
+
var ValidationResultSchema = import_zod5.z.object({
|
|
2767
|
+
valid: import_zod5.z.boolean(),
|
|
2768
|
+
errors: import_zod5.z.array(ValidationErrorSchema)
|
|
2769
|
+
});
|
|
2770
|
+
var ValidationOptionsSchema = import_zod5.z.object({
|
|
2771
|
+
validateConditions: import_zod5.z.boolean().optional(),
|
|
2772
|
+
validateConsequences: import_zod5.z.boolean().optional(),
|
|
2773
|
+
strictMode: import_zod5.z.boolean().optional()
|
|
2774
|
+
});
|
|
2775
|
+
var DEFAULT_OPTIONS4 = ValidationOptionsSchema.parse({});
|
|
2709
2776
|
var createError = (path, message, code) => ({
|
|
2710
2777
|
path,
|
|
2711
2778
|
message,
|
|
@@ -2717,7 +2784,7 @@ var validResult = () => ({
|
|
|
2717
2784
|
});
|
|
2718
2785
|
var invalidResult = (errors) => ({
|
|
2719
2786
|
valid: false,
|
|
2720
|
-
errors
|
|
2787
|
+
errors: [...errors]
|
|
2721
2788
|
});
|
|
2722
2789
|
var validateConditionStructure = (condition, path) => {
|
|
2723
2790
|
const errors = [];
|
|
@@ -2758,6 +2825,8 @@ var validateConsequenceStructure = (consequences, consequenceSchemas, strictMode
|
|
|
2758
2825
|
const errors = [];
|
|
2759
2826
|
for (let i = 0;i < consequences.length; i++) {
|
|
2760
2827
|
const consequence = consequences[i];
|
|
2828
|
+
if (!consequence)
|
|
2829
|
+
continue;
|
|
2761
2830
|
const path = `consequences[${i}]`;
|
|
2762
2831
|
if (!consequence.type || typeof consequence.type !== "string") {
|
|
2763
2832
|
errors.push(createError(`${path}.type`, "Consequence must have a type", "MISSING_CONSEQUENCE_TYPE"));
|
|
@@ -2794,8 +2863,8 @@ var validateRule = (rule2, options = {}) => {
|
|
|
2794
2863
|
const conditionErrors = validateConditionStructure(validRule.conditions, "conditions");
|
|
2795
2864
|
errors.push(...conditionErrors);
|
|
2796
2865
|
}
|
|
2797
|
-
if (opts.validateConsequences) {
|
|
2798
|
-
const consequenceErrors = validateConsequenceStructure(validRule.consequences, opts.consequenceSchemas, opts.strictMode);
|
|
2866
|
+
if (opts.validateConsequences || opts.strictMode) {
|
|
2867
|
+
const consequenceErrors = validateConsequenceStructure(validRule.consequences, opts.consequenceSchemas, opts.strictMode ?? false);
|
|
2799
2868
|
errors.push(...consequenceErrors);
|
|
2800
2869
|
}
|
|
2801
2870
|
return errors.length > 0 ? invalidResult(errors) : validResult();
|