@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/dist/index.js CHANGED
@@ -1,7 +1,32 @@
1
+ // src/index.ts
2
+ import {
3
+ ConditionGroup,
4
+ isConditionGroup as isConditionGroup6
5
+ } from "@f-o-t/condition-evaluator";
6
+
1
7
  // src/analyzer/analysis.ts
8
+ import {
9
+ isConditionGroup as isConditionGroup2
10
+ } from "@f-o-t/condition-evaluator";
11
+
12
+ // src/utils/conditions.ts
2
13
  import {
3
14
  isConditionGroup
4
15
  } from "@f-o-t/condition-evaluator";
16
+ var collectConditionFields = (condition) => {
17
+ const fields = new Set;
18
+ const traverse = (c) => {
19
+ if (isConditionGroup(c)) {
20
+ for (const child of c.conditions) {
21
+ traverse(child);
22
+ }
23
+ } else {
24
+ fields.add(c.field);
25
+ }
26
+ };
27
+ traverse(condition);
28
+ return fields;
29
+ };
5
30
  var countConditions = (condition) => {
6
31
  if (isConditionGroup(condition)) {
7
32
  return condition.conditions.reduce((sum, c) => sum + countConditions(c), 0);
@@ -16,30 +41,18 @@ var calculateMaxDepth = (condition, currentDepth = 1) => {
16
41
  }
17
42
  return currentDepth;
18
43
  };
19
- var countGroups = (condition) => {
44
+ var countConditionGroups = (condition) => {
20
45
  if (isConditionGroup(condition)) {
21
- return 1 + condition.conditions.reduce((sum, c) => sum + countGroups(c), 0);
46
+ return 1 + condition.conditions.reduce((sum, c) => sum + countConditionGroups(c), 0);
22
47
  }
23
48
  return 0;
24
49
  };
25
- var collectFields = (condition) => {
26
- const fields = new Set;
27
- const traverse = (c) => {
28
- if (isConditionGroup(c)) {
29
- for (const child of c.conditions) {
30
- traverse(child);
31
- }
32
- } else {
33
- fields.add(c.field);
34
- }
35
- };
36
- traverse(condition);
37
- return fields;
38
- };
50
+
51
+ // src/analyzer/analysis.ts
39
52
  var collectOperators = (condition) => {
40
53
  const operators = new Set;
41
54
  const traverse = (c) => {
42
- if (isConditionGroup(c)) {
55
+ if (isConditionGroup2(c)) {
43
56
  operators.add(c.operator);
44
57
  for (const child of c.conditions) {
45
58
  traverse(child);
@@ -57,8 +70,8 @@ var calculateComplexityScore = (totalConditions, maxDepth, groupCount, uniqueFie
57
70
  var analyzeRuleComplexity = (rule) => {
58
71
  const totalConditions = countConditions(rule.conditions);
59
72
  const maxDepth = calculateMaxDepth(rule.conditions);
60
- const groupCount = countGroups(rule.conditions);
61
- const uniqueFields = collectFields(rule.conditions).size;
73
+ const groupCount = countConditionGroups(rule.conditions);
74
+ const uniqueFields = collectConditionFields(rule.conditions).size;
62
75
  const uniqueOperators = collectOperators(rule.conditions).size;
63
76
  const consequenceCount = rule.consequences.length;
64
77
  const complexityScore = calculateComplexityScore(totalConditions, maxDepth, groupCount, uniqueFields);
@@ -95,7 +108,7 @@ var analyzeRuleSet = (rules) => {
95
108
  maxPriority = rule.priority;
96
109
  if (rule.enabled)
97
110
  enabledCount++;
98
- for (const field of collectFields(rule.conditions)) {
111
+ for (const field of collectConditionFields(rule.conditions)) {
99
112
  allFields.add(field);
100
113
  }
101
114
  for (const operator of collectOperators(rule.conditions)) {
@@ -139,7 +152,7 @@ var analyzeFieldUsage = (rules) => {
139
152
  const fieldMap = new Map;
140
153
  for (const rule of rules) {
141
154
  const traverse = (c) => {
142
- if (isConditionGroup(c)) {
155
+ if (isConditionGroup2(c)) {
143
156
  for (const child of c.conditions) {
144
157
  traverse(child);
145
158
  }
@@ -171,7 +184,7 @@ var analyzeOperatorUsage = (rules) => {
171
184
  const operatorMap = new Map;
172
185
  for (const rule of rules) {
173
186
  const traverse = (c) => {
174
- if (isConditionGroup(c)) {
187
+ if (isConditionGroup2(c)) {
175
188
  for (const child of c.conditions) {
176
189
  traverse(child);
177
190
  }
@@ -189,12 +202,15 @@ var analyzeOperatorUsage = (rules) => {
189
202
  };
190
203
  traverse(rule.conditions);
191
204
  }
192
- return [...operatorMap.entries()].map(([key, data]) => ({
193
- operator: key.split(":")[1],
194
- type: data.type,
195
- count: data.rules.length,
196
- rules: data.rules
197
- })).sort((a, b) => b.count - a.count);
205
+ return [...operatorMap.entries()].map(([key, data]) => {
206
+ const parts = key.split(":");
207
+ return {
208
+ operator: parts[1] ?? "",
209
+ type: data.type,
210
+ count: data.rules.length,
211
+ rules: data.rules
212
+ };
213
+ }).sort((a, b) => b.count - a.count);
198
214
  };
199
215
  var analyzeConsequenceUsage = (rules) => {
200
216
  const consequenceMap = new Map;
@@ -483,15 +499,8 @@ var createCache = (options) => {
483
499
  const evictOldest = () => {
484
500
  if (entries.size === 0)
485
501
  return;
486
- let oldestKey;
487
- let oldestTime = Number.POSITIVE_INFINITY;
488
- for (const [key, entry] of entries) {
489
- if (entry.createdAt < oldestTime) {
490
- oldestTime = entry.createdAt;
491
- oldestKey = key;
492
- }
493
- }
494
- if (oldestKey) {
502
+ const oldestKey = entries.keys().next().value;
503
+ if (oldestKey !== undefined) {
495
504
  const entry = entries.get(oldestKey);
496
505
  entries.delete(oldestKey);
497
506
  evictions++;
@@ -618,9 +627,6 @@ var withTimeout = (promise, timeoutMs, errorMessage = "Operation timed out") =>
618
627
  });
619
628
  });
620
629
  };
621
- var delay = (ms) => {
622
- return new Promise((resolve) => setTimeout(resolve, ms));
623
- };
624
630
 
625
631
  // src/core/evaluate.ts
626
632
  var evaluateRule = (rule2, context, options = {}) => {
@@ -854,30 +860,67 @@ var sortByUpdatedAt = (direction = "desc") => {
854
860
  return sortRules({ field: "updatedAt", direction });
855
861
  };
856
862
  // src/types/config.ts
857
- var DEFAULT_CACHE_CONFIG = {
858
- enabled: true,
859
- ttl: 60000,
860
- maxSize: 1000
861
- };
862
- var DEFAULT_VALIDATION_CONFIG = {
863
- enabled: true,
864
- strict: false
865
- };
866
- var DEFAULT_VERSIONING_CONFIG = {
867
- enabled: false,
868
- maxVersions: 10
869
- };
870
- var DEFAULT_ENGINE_CONFIG = {
871
- conflictResolution: "priority",
872
- cache: DEFAULT_CACHE_CONFIG,
873
- validation: DEFAULT_VALIDATION_CONFIG,
874
- versioning: DEFAULT_VERSIONING_CONFIG,
875
- logLevel: "warn",
876
- continueOnError: true,
877
- slowRuleThresholdMs: 10
878
- };
863
+ import { z } from "zod";
864
+ var LogLevelSchema = z.enum([
865
+ "none",
866
+ "error",
867
+ "warn",
868
+ "info",
869
+ "debug"
870
+ ]);
871
+ var CacheConfigSchema = z.object({
872
+ enabled: z.boolean().default(true),
873
+ ttl: z.number().int().positive().default(60000),
874
+ maxSize: z.number().int().positive().default(1000)
875
+ });
876
+ var ValidationConfigSchema = z.object({
877
+ enabled: z.boolean().default(true),
878
+ strict: z.boolean().default(false)
879
+ });
880
+ var VersioningConfigSchema = z.object({
881
+ enabled: z.boolean().default(false),
882
+ maxVersions: z.number().int().positive().default(10)
883
+ });
884
+ var parseCacheConfig = (input) => CacheConfigSchema.parse(input ?? {});
885
+ var parseValidationConfig = (input) => ValidationConfigSchema.parse(input ?? {});
886
+ var parseVersioningConfig = (input) => VersioningConfigSchema.parse(input ?? {});
887
+ var getDefaultCacheConfig = () => CacheConfigSchema.parse({});
888
+ var getDefaultValidationConfig = () => ValidationConfigSchema.parse({});
889
+ var getDefaultVersioningConfig = () => VersioningConfigSchema.parse({});
890
+ var getDefaultLogLevel = () => "warn";
891
+ var getDefaultConflictResolution = () => "priority";
879
892
 
880
893
  // src/types/state.ts
894
+ import { z as z2 } from "zod";
895
+ var RuleStatsSchema = z2.object({
896
+ evaluations: z2.number().int().nonnegative(),
897
+ matches: z2.number().int().nonnegative(),
898
+ errors: z2.number().int().nonnegative(),
899
+ totalTimeMs: z2.number().nonnegative(),
900
+ avgTimeMs: z2.number().nonnegative(),
901
+ lastEvaluated: z2.date().optional()
902
+ });
903
+ var CacheStatsSchema = z2.object({
904
+ size: z2.number().int().nonnegative(),
905
+ maxSize: z2.number().int().positive(),
906
+ hits: z2.number().int().nonnegative(),
907
+ misses: z2.number().int().nonnegative(),
908
+ hitRate: z2.number().min(0).max(1),
909
+ evictions: z2.number().int().nonnegative()
910
+ });
911
+ var EngineStatsSchema = z2.object({
912
+ totalRules: z2.number().int().nonnegative(),
913
+ enabledRules: z2.number().int().nonnegative(),
914
+ disabledRules: z2.number().int().nonnegative(),
915
+ totalRuleSets: z2.number().int().nonnegative(),
916
+ totalEvaluations: z2.number().int().nonnegative(),
917
+ totalMatches: z2.number().int().nonnegative(),
918
+ totalErrors: z2.number().int().nonnegative(),
919
+ avgEvaluationTimeMs: z2.number().nonnegative(),
920
+ cacheHits: z2.number().int().nonnegative(),
921
+ cacheMisses: z2.number().int().nonnegative(),
922
+ cacheHitRate: z2.number().min(0).max(1)
923
+ });
881
924
  var createInitialState = () => ({
882
925
  rules: new Map,
883
926
  ruleSets: new Map,
@@ -934,82 +977,73 @@ var generateId = () => {
934
977
  };
935
978
 
936
979
  // src/engine/hooks.ts
937
- var executeBeforeEvaluation = async (hooks, context, rules) => {
980
+ var toError = (error) => error instanceof Error ? error : new Error(String(error));
981
+ var executeWithTimeout = async (hookName, hookFn, hooks, timeoutMs) => {
982
+ try {
983
+ const promise = Promise.resolve(hookFn());
984
+ if (timeoutMs !== undefined && timeoutMs > 0) {
985
+ await withTimeout(promise, timeoutMs, `Hook '${hookName}' timed out after ${timeoutMs}ms`);
986
+ } else {
987
+ await promise;
988
+ }
989
+ } catch (error) {
990
+ hooks.onHookError?.(hookName, toError(error));
991
+ }
992
+ };
993
+ var executeBeforeEvaluation = async (hooks, context, rules, timeoutMs) => {
938
994
  if (!hooks.beforeEvaluation)
939
995
  return;
940
- try {
941
- await hooks.beforeEvaluation(context, rules);
942
- } catch {}
996
+ await executeWithTimeout("beforeEvaluation", () => hooks.beforeEvaluation(context, rules), hooks, timeoutMs);
943
997
  };
944
- var executeAfterEvaluation = async (hooks, result) => {
998
+ var executeAfterEvaluation = async (hooks, result, timeoutMs) => {
945
999
  if (!hooks.afterEvaluation)
946
1000
  return;
947
- try {
948
- await hooks.afterEvaluation(result);
949
- } catch {}
1001
+ await executeWithTimeout("afterEvaluation", () => hooks.afterEvaluation(result), hooks, timeoutMs);
950
1002
  };
951
- var executeBeforeRuleEvaluation = async (hooks, rule2, context) => {
1003
+ var executeBeforeRuleEvaluation = async (hooks, rule2, context, timeoutMs) => {
952
1004
  if (!hooks.beforeRuleEvaluation)
953
1005
  return;
954
- try {
955
- await hooks.beforeRuleEvaluation(rule2, context);
956
- } catch {}
1006
+ await executeWithTimeout("beforeRuleEvaluation", () => hooks.beforeRuleEvaluation(rule2, context), hooks, timeoutMs);
957
1007
  };
958
- var executeAfterRuleEvaluation = async (hooks, rule2, result) => {
1008
+ var executeAfterRuleEvaluation = async (hooks, rule2, result, timeoutMs) => {
959
1009
  if (!hooks.afterRuleEvaluation)
960
1010
  return;
961
- try {
962
- await hooks.afterRuleEvaluation(rule2, result);
963
- } catch {}
1011
+ await executeWithTimeout("afterRuleEvaluation", () => hooks.afterRuleEvaluation(rule2, result), hooks, timeoutMs);
964
1012
  };
965
- var executeOnRuleMatch = async (hooks, rule2, context) => {
1013
+ var executeOnRuleMatch = async (hooks, rule2, context, timeoutMs) => {
966
1014
  if (!hooks.onRuleMatch)
967
1015
  return;
968
- try {
969
- await hooks.onRuleMatch(rule2, context);
970
- } catch {}
1016
+ await executeWithTimeout("onRuleMatch", () => hooks.onRuleMatch(rule2, context), hooks, timeoutMs);
971
1017
  };
972
- var executeOnRuleSkip = async (hooks, rule2, reason) => {
1018
+ var executeOnRuleSkip = async (hooks, rule2, reason, timeoutMs) => {
973
1019
  if (!hooks.onRuleSkip)
974
1020
  return;
975
- try {
976
- await hooks.onRuleSkip(rule2, reason);
977
- } catch {}
1021
+ await executeWithTimeout("onRuleSkip", () => hooks.onRuleSkip(rule2, reason), hooks, timeoutMs);
978
1022
  };
979
- var executeOnRuleError = async (hooks, rule2, error) => {
1023
+ var executeOnRuleError = async (hooks, rule2, ruleError, timeoutMs) => {
980
1024
  if (!hooks.onRuleError)
981
1025
  return;
982
- try {
983
- await hooks.onRuleError(rule2, error);
984
- } catch {}
1026
+ await executeWithTimeout("onRuleError", () => hooks.onRuleError(rule2, ruleError), hooks, timeoutMs);
985
1027
  };
986
- var executeOnConsequenceCollected = async (hooks, rule2, consequence) => {
1028
+ var executeOnConsequenceCollected = async (hooks, rule2, consequence, timeoutMs) => {
987
1029
  if (!hooks.onConsequenceCollected)
988
1030
  return;
989
- try {
990
- await hooks.onConsequenceCollected(rule2, consequence);
991
- } catch {}
1031
+ await executeWithTimeout("onConsequenceCollected", () => hooks.onConsequenceCollected(rule2, consequence), hooks, timeoutMs);
992
1032
  };
993
- var executeOnCacheHit = async (hooks, key, result) => {
1033
+ var executeOnCacheHit = async (hooks, key, result, timeoutMs) => {
994
1034
  if (!hooks.onCacheHit)
995
1035
  return;
996
- try {
997
- await hooks.onCacheHit(key, result);
998
- } catch {}
1036
+ await executeWithTimeout("onCacheHit", () => hooks.onCacheHit(key, result), hooks, timeoutMs);
999
1037
  };
1000
- var executeOnCacheMiss = async (hooks, key) => {
1038
+ var executeOnCacheMiss = async (hooks, key, timeoutMs) => {
1001
1039
  if (!hooks.onCacheMiss)
1002
1040
  return;
1003
- try {
1004
- await hooks.onCacheMiss(key);
1005
- } catch {}
1041
+ await executeWithTimeout("onCacheMiss", () => hooks.onCacheMiss(key), hooks, timeoutMs);
1006
1042
  };
1007
- var executeOnSlowRule = async (hooks, rule2, timeMs, threshold) => {
1043
+ var executeOnSlowRule = async (hooks, rule2, timeMs, threshold, timeoutMs) => {
1008
1044
  if (!hooks.onSlowRule)
1009
1045
  return;
1010
- try {
1011
- await hooks.onSlowRule(rule2, timeMs, threshold);
1012
- } catch {}
1046
+ await executeWithTimeout("onSlowRule", () => hooks.onSlowRule(rule2, timeMs, threshold), hooks, timeoutMs);
1013
1047
  };
1014
1048
 
1015
1049
  // src/engine/state.ts
@@ -1194,24 +1228,25 @@ var cloneState = (state) => {
1194
1228
  var resolveConfig = (config) => {
1195
1229
  return {
1196
1230
  consequences: config.consequences,
1197
- conflictResolution: config.conflictResolution ?? DEFAULT_ENGINE_CONFIG.conflictResolution,
1231
+ conflictResolution: config.conflictResolution ?? getDefaultConflictResolution(),
1198
1232
  cache: {
1199
- ...DEFAULT_CACHE_CONFIG,
1233
+ ...getDefaultCacheConfig(),
1200
1234
  ...config.cache
1201
1235
  },
1202
1236
  validation: {
1203
- ...DEFAULT_VALIDATION_CONFIG,
1237
+ ...getDefaultValidationConfig(),
1204
1238
  ...config.validation
1205
1239
  },
1206
1240
  versioning: {
1207
- ...DEFAULT_VERSIONING_CONFIG,
1241
+ ...getDefaultVersioningConfig(),
1208
1242
  ...config.versioning
1209
1243
  },
1210
1244
  hooks: config.hooks ?? {},
1211
- logLevel: config.logLevel ?? DEFAULT_ENGINE_CONFIG.logLevel,
1245
+ logLevel: config.logLevel ?? getDefaultLogLevel(),
1212
1246
  logger: config.logger ?? console,
1213
- continueOnError: config.continueOnError ?? DEFAULT_ENGINE_CONFIG.continueOnError,
1214
- slowRuleThresholdMs: config.slowRuleThresholdMs ?? DEFAULT_ENGINE_CONFIG.slowRuleThresholdMs
1247
+ continueOnError: config.continueOnError ?? true,
1248
+ slowRuleThresholdMs: config.slowRuleThresholdMs ?? 10,
1249
+ hookTimeoutMs: config.hookTimeoutMs
1215
1250
  };
1216
1251
  };
1217
1252
  var createEngine = (config = {}) => {
@@ -1243,12 +1278,6 @@ var createEngine = (config = {}) => {
1243
1278
  const ruleSetIds = new Set(ruleSetRules.map((r) => r.id));
1244
1279
  rulesToEvaluate = rulesToEvaluate.filter((r) => ruleSetIds.has(r.id));
1245
1280
  }
1246
- rulesToEvaluate = [
1247
- ...sortRules({
1248
- field: "priority",
1249
- direction: "desc"
1250
- })(rulesToEvaluate)
1251
- ];
1252
1281
  if (options.maxRules && options.maxRules > 0) {
1253
1282
  rulesToEvaluate = rulesToEvaluate.slice(0, options.maxRules);
1254
1283
  }
@@ -1257,15 +1286,15 @@ var createEngine = (config = {}) => {
1257
1286
  const cached = cache.get(cacheKey);
1258
1287
  if (cached) {
1259
1288
  cacheHits++;
1260
- await executeOnCacheHit(resolvedConfig.hooks, cacheKey, cached);
1289
+ await executeOnCacheHit(resolvedConfig.hooks, cacheKey, cached, resolvedConfig.hookTimeoutMs);
1261
1290
  return { ...cached, cacheHit: true };
1262
1291
  }
1263
1292
  }
1264
1293
  if (cacheKey) {
1265
1294
  cacheMisses++;
1266
- await executeOnCacheMiss(resolvedConfig.hooks, cacheKey);
1295
+ await executeOnCacheMiss(resolvedConfig.hooks, cacheKey, resolvedConfig.hookTimeoutMs);
1267
1296
  }
1268
- await executeBeforeEvaluation(resolvedConfig.hooks, context, rulesToEvaluate);
1297
+ await executeBeforeEvaluation(resolvedConfig.hooks, context, rulesToEvaluate, resolvedConfig.hookTimeoutMs);
1269
1298
  const { result: evaluationResult, durationMs } = measureTime(() => {
1270
1299
  const results = [];
1271
1300
  for (const rule2 of rulesToEvaluate) {
@@ -1282,29 +1311,29 @@ var createEngine = (config = {}) => {
1282
1311
  let rulesErrored = 0;
1283
1312
  const conflictResolution = options.conflictResolution ?? resolvedConfig.conflictResolution;
1284
1313
  for (const { rule: rule2, result } of evaluationResult) {
1285
- await executeBeforeRuleEvaluation(resolvedConfig.hooks, rule2, context);
1314
+ await executeBeforeRuleEvaluation(resolvedConfig.hooks, rule2, context, resolvedConfig.hookTimeoutMs);
1286
1315
  ruleResults.push(result);
1287
1316
  if (result.error) {
1288
1317
  rulesErrored++;
1289
- await executeOnRuleError(resolvedConfig.hooks, rule2, result.error);
1318
+ await executeOnRuleError(resolvedConfig.hooks, rule2, result.error, resolvedConfig.hookTimeoutMs);
1290
1319
  if (!resolvedConfig.continueOnError) {
1291
1320
  break;
1292
1321
  }
1293
1322
  }
1294
1323
  if (result.skipped) {
1295
- await executeOnRuleSkip(resolvedConfig.hooks, rule2, result.skipReason ?? "Unknown");
1324
+ await executeOnRuleSkip(resolvedConfig.hooks, rule2, result.skipReason ?? "Unknown", resolvedConfig.hookTimeoutMs);
1296
1325
  }
1297
1326
  if (result.evaluationTimeMs > resolvedConfig.slowRuleThresholdMs) {
1298
- await executeOnSlowRule(resolvedConfig.hooks, rule2, result.evaluationTimeMs, resolvedConfig.slowRuleThresholdMs);
1327
+ await executeOnSlowRule(resolvedConfig.hooks, rule2, result.evaluationTimeMs, resolvedConfig.slowRuleThresholdMs, resolvedConfig.hookTimeoutMs);
1299
1328
  }
1300
- await executeAfterRuleEvaluation(resolvedConfig.hooks, rule2, result);
1329
+ await executeAfterRuleEvaluation(resolvedConfig.hooks, rule2, result, resolvedConfig.hookTimeoutMs);
1301
1330
  if (result.matched) {
1302
1331
  matchedRules.push(rule2);
1303
1332
  for (const consequence of result.consequences) {
1304
1333
  consequences.push(consequence);
1305
- await executeOnConsequenceCollected(resolvedConfig.hooks, rule2, consequence);
1334
+ await executeOnConsequenceCollected(resolvedConfig.hooks, rule2, consequence, resolvedConfig.hookTimeoutMs);
1306
1335
  }
1307
- await executeOnRuleMatch(resolvedConfig.hooks, rule2, context);
1336
+ await executeOnRuleMatch(resolvedConfig.hooks, rule2, context, resolvedConfig.hookTimeoutMs);
1308
1337
  if (rule2.stopOnMatch) {
1309
1338
  stoppedEarly = true;
1310
1339
  stoppedByRuleId = rule2.id;
@@ -1338,7 +1367,7 @@ var createEngine = (config = {}) => {
1338
1367
  if (cacheKey) {
1339
1368
  cache.set(cacheKey, executionResult);
1340
1369
  }
1341
- await executeAfterEvaluation(resolvedConfig.hooks, executionResult);
1370
+ await executeAfterEvaluation(resolvedConfig.hooks, executionResult, resolvedConfig.hookTimeoutMs);
1342
1371
  return executionResult;
1343
1372
  };
1344
1373
  return {
@@ -1388,7 +1417,6 @@ var createEngine = (config = {}) => {
1388
1417
  }),
1389
1418
  getStats: () => {
1390
1419
  const enabledRules = Array.from(state.rules.values()).filter((r) => r.enabled).length;
1391
- const cacheStats = cache.getStats();
1392
1420
  return {
1393
1421
  totalRules: state.rules.size,
1394
1422
  enabledRules,
@@ -1406,29 +1434,12 @@ var createEngine = (config = {}) => {
1406
1434
  };
1407
1435
  };
1408
1436
  // src/optimizer/index-builder.ts
1409
- import {
1410
- isConditionGroup as isConditionGroup2
1411
- } from "@f-o-t/condition-evaluator";
1412
1437
  var DEFAULT_OPTIONS = {
1413
1438
  indexByField: true,
1414
1439
  indexByTag: true,
1415
1440
  indexByCategory: true,
1416
1441
  indexByPriority: true
1417
1442
  };
1418
- var collectFields2 = (condition) => {
1419
- const fields = new Set;
1420
- const traverse = (c) => {
1421
- if (isConditionGroup2(c)) {
1422
- for (const child of c.conditions) {
1423
- traverse(child);
1424
- }
1425
- } else {
1426
- fields.add(c.field);
1427
- }
1428
- };
1429
- traverse(condition);
1430
- return fields;
1431
- };
1432
1443
  var buildIndex = (rules, options = {}) => {
1433
1444
  const opts = { ...DEFAULT_OPTIONS, ...options };
1434
1445
  const byId = new Map;
@@ -1439,7 +1450,7 @@ var buildIndex = (rules, options = {}) => {
1439
1450
  for (const rule2 of rules) {
1440
1451
  byId.set(rule2.id, rule2);
1441
1452
  if (opts.indexByField) {
1442
- const fields = collectFields2(rule2.conditions);
1453
+ const fields = collectConditionFields(rule2.conditions);
1443
1454
  for (const field of fields) {
1444
1455
  const existing = byField.get(field) ?? [];
1445
1456
  existing.push(rule2);
@@ -1544,7 +1555,7 @@ var analyzeOptimizations = (rules) => {
1544
1555
  const suggestions = [];
1545
1556
  const fieldUsage = new Map;
1546
1557
  for (const rule2 of rules) {
1547
- const fields = collectFields2(rule2.conditions);
1558
+ const fields = collectConditionFields(rule2.conditions);
1548
1559
  for (const field of fields) {
1549
1560
  fieldUsage.set(field, (fieldUsage.get(field) ?? 0) + 1);
1550
1561
  }
@@ -1697,6 +1708,8 @@ var importRules = (data, options = {}) => {
1697
1708
  for (let i = 0;i < data.rules.length; i++) {
1698
1709
  try {
1699
1710
  const serialized = data.rules[i];
1711
+ if (!serialized)
1712
+ continue;
1700
1713
  const rule2 = deserializeRule(serialized, options);
1701
1714
  idMapping.set(serialized.id, rule2.id);
1702
1715
  rules.push(rule2);
@@ -1712,6 +1725,8 @@ var importRules = (data, options = {}) => {
1712
1725
  for (let i = 0;i < data.ruleSets.length; i++) {
1713
1726
  try {
1714
1727
  const serialized = data.ruleSets[i];
1728
+ if (!serialized)
1729
+ continue;
1715
1730
  const ruleSet = deserializeRuleSet(serialized, idMapping, options);
1716
1731
  ruleSets.push(ruleSet);
1717
1732
  } catch (error) {
@@ -1723,12 +1738,25 @@ var importRules = (data, options = {}) => {
1723
1738
  }
1724
1739
  }
1725
1740
  }
1741
+ const importedRuleIds = new Set(rules.map((r) => r.id));
1742
+ const orphanedReferences = [];
1743
+ for (const ruleSet of ruleSets) {
1744
+ const missingRuleIds = ruleSet.ruleIds.filter((id) => !importedRuleIds.has(id));
1745
+ if (missingRuleIds.length > 0) {
1746
+ orphanedReferences.push({
1747
+ ruleSetId: ruleSet.id,
1748
+ ruleSetName: ruleSet.name,
1749
+ missingRuleIds
1750
+ });
1751
+ }
1752
+ }
1726
1753
  return {
1727
1754
  success: errors.length === 0,
1728
1755
  rules,
1729
1756
  ruleSets,
1730
1757
  errors,
1731
- idMapping
1758
+ idMapping,
1759
+ orphanedReferences
1732
1760
  };
1733
1761
  };
1734
1762
  var importFromJson = (json, options = {}) => {
@@ -1747,7 +1775,8 @@ var importFromJson = (json, options = {}) => {
1747
1775
  message: `Invalid JSON: ${error instanceof Error ? error.message : String(error)}`
1748
1776
  }
1749
1777
  ],
1750
- idMapping: new Map
1778
+ idMapping: new Map,
1779
+ orphanedReferences: []
1751
1780
  };
1752
1781
  }
1753
1782
  };
@@ -1884,8 +1913,8 @@ var whatIf = (originalRules, modifiedRules, context) => {
1884
1913
  const [ruleId, consequenceType] = key.split(":");
1885
1914
  consequenceChanges.push({
1886
1915
  type: "added",
1887
- consequenceType,
1888
- ruleId
1916
+ consequenceType: consequenceType ?? "",
1917
+ ruleId: ruleId ?? ""
1889
1918
  });
1890
1919
  }
1891
1920
  }
@@ -1894,8 +1923,8 @@ var whatIf = (originalRules, modifiedRules, context) => {
1894
1923
  const [ruleId, consequenceType] = key.split(":");
1895
1924
  consequenceChanges.push({
1896
1925
  type: "removed",
1897
- consequenceType,
1898
- ruleId
1926
+ consequenceType: consequenceType ?? "",
1927
+ ruleId: ruleId ?? ""
1899
1928
  });
1900
1929
  }
1901
1930
  }
@@ -1984,49 +2013,59 @@ var formatSimulationResult = (result) => {
1984
2013
  return lines.join(`
1985
2014
  `);
1986
2015
  };
2016
+ // src/types/evaluation.ts
2017
+ import { z as z3 } from "zod";
2018
+ var ConflictResolutionStrategySchema = z3.enum([
2019
+ "priority",
2020
+ "first-match",
2021
+ "all",
2022
+ "most-specific"
2023
+ ]);
2024
+ var EvaluateOptionsSchema = z3.object({
2025
+ conflictResolution: ConflictResolutionStrategySchema.optional(),
2026
+ maxRules: z3.number().int().positive().optional(),
2027
+ timeout: z3.number().int().positive().optional(),
2028
+ skipDisabled: z3.boolean().optional(),
2029
+ tags: z3.array(z3.string()).optional(),
2030
+ category: z3.string().optional(),
2031
+ ruleSetId: z3.string().optional(),
2032
+ bypassCache: z3.boolean().optional()
2033
+ });
2034
+ var EvaluateConfigSchema = z3.object({
2035
+ conflictResolution: ConflictResolutionStrategySchema,
2036
+ continueOnError: z3.boolean(),
2037
+ collectAllConsequences: z3.boolean()
2038
+ });
1987
2039
  // src/types/rule.ts
1988
- import { z } from "zod";
1989
- var RuleSchema = z.object({
1990
- id: z.string().min(1),
1991
- name: z.string().min(1),
1992
- description: z.string().optional(),
1993
- conditions: z.custom((val) => {
2040
+ import { z as z4 } from "zod";
2041
+ var RuleSchema = z4.object({
2042
+ id: z4.string().min(1),
2043
+ name: z4.string().min(1),
2044
+ description: z4.string().optional(),
2045
+ conditions: z4.custom((val) => {
1994
2046
  return typeof val === "object" && val !== null && "id" in val && "operator" in val;
1995
2047
  }, "Invalid condition group"),
1996
- consequences: z.array(z.object({
1997
- type: z.string(),
1998
- payload: z.unknown()
2048
+ consequences: z4.array(z4.object({
2049
+ type: z4.string(),
2050
+ payload: z4.unknown()
1999
2051
  })),
2000
- priority: z.number().int().default(0),
2001
- enabled: z.boolean().default(true),
2002
- stopOnMatch: z.boolean().default(false),
2003
- tags: z.array(z.string()).default([]),
2004
- category: z.string().optional(),
2005
- metadata: z.record(z.string(), z.unknown()).optional(),
2006
- createdAt: z.date().default(() => new Date),
2007
- updatedAt: z.date().default(() => new Date)
2052
+ priority: z4.number().int().default(0),
2053
+ enabled: z4.boolean().default(true),
2054
+ stopOnMatch: z4.boolean().default(false),
2055
+ tags: z4.array(z4.string()).default([]),
2056
+ category: z4.string().optional(),
2057
+ metadata: z4.record(z4.string(), z4.unknown()).optional(),
2058
+ createdAt: z4.date().default(() => new Date),
2059
+ updatedAt: z4.date().default(() => new Date)
2008
2060
  });
2009
- var RuleSetSchema = z.object({
2010
- id: z.string().min(1),
2011
- name: z.string().min(1),
2012
- description: z.string().optional(),
2013
- ruleIds: z.array(z.string()),
2014
- enabled: z.boolean().default(true),
2015
- metadata: z.record(z.string(), z.unknown()).optional()
2061
+ var RuleSetSchema = z4.object({
2062
+ id: z4.string().min(1),
2063
+ name: z4.string().min(1),
2064
+ description: z4.string().optional(),
2065
+ ruleIds: z4.array(z4.string()),
2066
+ enabled: z4.boolean().default(true),
2067
+ metadata: z4.record(z4.string(), z4.unknown()).optional()
2016
2068
  });
2017
- // src/utils/pipe.ts
2018
- function pipe(...fns) {
2019
- return (value) => fns.reduce((acc, fn) => fn(acc), value);
2020
- }
2021
- function compose(...fns) {
2022
- return (value) => fns.reduceRight((acc, fn) => fn(acc), value);
2023
- }
2024
- var identity = (value) => value;
2025
- var always = (value) => () => value;
2026
- var tap = (fn) => (value) => {
2027
- fn(value);
2028
- return value;
2029
- };
2030
2069
  // src/validation/conflicts.ts
2031
2070
  import {
2032
2071
  isConditionGroup as isConditionGroup3
@@ -2038,11 +2077,11 @@ var DEFAULT_OPTIONS2 = {
2038
2077
  checkPriorityCollisions: true,
2039
2078
  checkUnreachableRules: true
2040
2079
  };
2041
- var collectConditionFields = (condition) => {
2080
+ var collectConditionFields2 = (condition) => {
2042
2081
  const fields = new Set;
2043
2082
  if (isConditionGroup3(condition)) {
2044
2083
  for (const child of condition.conditions) {
2045
- const childFields = collectConditionFields(child);
2084
+ const childFields = collectConditionFields2(child);
2046
2085
  for (const field of childFields) {
2047
2086
  fields.add(field);
2048
2087
  }
@@ -2077,8 +2116,8 @@ var getConditionOperatorValues = (condition, field) => {
2077
2116
  return results;
2078
2117
  };
2079
2118
  var areConditionsOverlapping = (conditions1, conditions2) => {
2080
- const fields1 = collectConditionFields(conditions1);
2081
- const fields2 = collectConditionFields(conditions2);
2119
+ const fields1 = collectConditionFields2(conditions1);
2120
+ const fields2 = collectConditionFields2(conditions2);
2082
2121
  const commonFields = new Set([...fields1].filter((f) => fields2.has(f)));
2083
2122
  if (commonFields.size === 0)
2084
2123
  return false;
@@ -2161,6 +2200,8 @@ var findOverlappingConditions = (rules) => {
2161
2200
  for (let j = i + 1;j < rules.length; j++) {
2162
2201
  const rule1 = rules[i];
2163
2202
  const rule2 = rules[j];
2203
+ if (!rule1 || !rule2)
2204
+ continue;
2164
2205
  const key = [rule1.id, rule2.id].sort().join(":");
2165
2206
  if (checked.has(key))
2166
2207
  continue;
@@ -2173,8 +2214,8 @@ var findOverlappingConditions = (rules) => {
2173
2214
  ruleIds: [rule1.id, rule2.id],
2174
2215
  rules: [rule1, rule2],
2175
2216
  details: {
2176
- rule1Fields: [...collectConditionFields(rule1.conditions)],
2177
- rule2Fields: [...collectConditionFields(rule2.conditions)]
2217
+ rule1Fields: [...collectConditionFields2(rule1.conditions)],
2218
+ rule2Fields: [...collectConditionFields2(rule2.conditions)]
2178
2219
  }
2179
2220
  });
2180
2221
  }
@@ -2197,6 +2238,8 @@ var findPriorityCollisions = (rules) => {
2197
2238
  for (let j = i + 1;j < rulesWithPriority.length; j++) {
2198
2239
  const r1 = rulesWithPriority[i];
2199
2240
  const r2 = rulesWithPriority[j];
2241
+ if (!r1 || !r2)
2242
+ continue;
2200
2243
  if (areConditionsOverlapping(r1.conditions, r2.conditions)) {
2201
2244
  overlappingPairs.add(r1.id);
2202
2245
  overlappingPairs.add(r2.id);
@@ -2223,10 +2266,14 @@ var findUnreachableRules = (rules) => {
2223
2266
  const sortedRules = [...rules].sort((a, b) => b.priority - a.priority);
2224
2267
  for (let i = 0;i < sortedRules.length; i++) {
2225
2268
  const rule2 = sortedRules[i];
2269
+ if (!rule2)
2270
+ continue;
2226
2271
  if (!rule2.enabled)
2227
2272
  continue;
2228
2273
  for (let j = 0;j < i; j++) {
2229
2274
  const higherPriorityRule = sortedRules[j];
2275
+ if (!higherPriorityRule)
2276
+ continue;
2230
2277
  if (!higherPriorityRule.enabled || !higherPriorityRule.stopOnMatch) {
2231
2278
  continue;
2232
2279
  }
@@ -2367,7 +2414,7 @@ var checkRuleIntegrity = (rule2, options) => {
2367
2414
  }
2368
2415
  }
2369
2416
  if (options.allowedTags) {
2370
- const invalidTags = rule2.tags.filter((t) => !options.allowedTags.includes(t));
2417
+ const invalidTags = rule2.tags.filter((t) => !options.allowedTags?.includes(t));
2371
2418
  if (invalidTags.length > 0) {
2372
2419
  issues.push(createIssue("INVALID_TAGS", `Rule "${rule2.name}" has invalid tags: ${invalidTags.join(", ")}`, "warning", {
2373
2420
  ruleId: rule2.id,
@@ -2542,11 +2589,22 @@ ${severity.toUpperCase()}S (${issues.length}):`);
2542
2589
  import {
2543
2590
  isConditionGroup as isConditionGroup5
2544
2591
  } from "@f-o-t/condition-evaluator";
2545
- var DEFAULT_OPTIONS4 = {
2546
- validateConditions: true,
2547
- validateConsequences: true,
2548
- strictMode: false
2549
- };
2592
+ import { z as z5 } from "zod";
2593
+ var ValidationErrorSchema = z5.object({
2594
+ path: z5.string(),
2595
+ message: z5.string(),
2596
+ code: z5.string()
2597
+ });
2598
+ var ValidationResultSchema = z5.object({
2599
+ valid: z5.boolean(),
2600
+ errors: z5.array(ValidationErrorSchema)
2601
+ });
2602
+ var ValidationOptionsSchema = z5.object({
2603
+ validateConditions: z5.boolean().optional(),
2604
+ validateConsequences: z5.boolean().optional(),
2605
+ strictMode: z5.boolean().optional()
2606
+ });
2607
+ var DEFAULT_OPTIONS4 = ValidationOptionsSchema.parse({});
2550
2608
  var createError = (path, message, code) => ({
2551
2609
  path,
2552
2610
  message,
@@ -2558,7 +2616,7 @@ var validResult = () => ({
2558
2616
  });
2559
2617
  var invalidResult = (errors) => ({
2560
2618
  valid: false,
2561
- errors
2619
+ errors: [...errors]
2562
2620
  });
2563
2621
  var validateConditionStructure = (condition, path) => {
2564
2622
  const errors = [];
@@ -2599,6 +2657,8 @@ var validateConsequenceStructure = (consequences, consequenceSchemas, strictMode
2599
2657
  const errors = [];
2600
2658
  for (let i = 0;i < consequences.length; i++) {
2601
2659
  const consequence = consequences[i];
2660
+ if (!consequence)
2661
+ continue;
2602
2662
  const path = `consequences[${i}]`;
2603
2663
  if (!consequence.type || typeof consequence.type !== "string") {
2604
2664
  errors.push(createError(`${path}.type`, "Consequence must have a type", "MISSING_CONSEQUENCE_TYPE"));
@@ -2635,8 +2695,8 @@ var validateRule = (rule2, options = {}) => {
2635
2695
  const conditionErrors = validateConditionStructure(validRule.conditions, "conditions");
2636
2696
  errors.push(...conditionErrors);
2637
2697
  }
2638
- if (opts.validateConsequences) {
2639
- const consequenceErrors = validateConsequenceStructure(validRule.consequences, opts.consequenceSchemas, opts.strictMode);
2698
+ if (opts.validateConsequences || opts.strictMode) {
2699
+ const consequenceErrors = validateConsequenceStructure(validRule.consequences, opts.consequenceSchemas, opts.strictMode ?? false);
2640
2700
  errors.push(...consequenceErrors);
2641
2701
  }
2642
2702
  return errors.length > 0 ? invalidResult(errors) : validResult();
@@ -2867,7 +2927,6 @@ export {
2867
2927
  validateRule,
2868
2928
  validateConditions,
2869
2929
  updateRule,
2870
- tap,
2871
2930
  str,
2872
2931
  sortRules,
2873
2932
  sortByUpdatedAt,
@@ -2885,16 +2944,18 @@ export {
2885
2944
  removeRuleSet,
2886
2945
  removeRule,
2887
2946
  pruneOldVersions,
2888
- pipe,
2947
+ parseVersioningConfig,
2948
+ parseValidationConfig,
2889
2949
  parseRule,
2950
+ parseCacheConfig,
2890
2951
  or,
2891
2952
  num,
2892
2953
  mergeRuleSets,
2893
2954
  measureTimeAsync,
2894
2955
  measureTime,
2956
+ isConditionGroup6 as isConditionGroup,
2895
2957
  importRules,
2896
2958
  importFromJson,
2897
- identity,
2898
2959
  hashRules,
2899
2960
  hashContext,
2900
2961
  hasErrors,
@@ -2925,6 +2986,11 @@ export {
2925
2986
  getLatestVersion,
2926
2987
  getIndexStats,
2927
2988
  getHistory,
2989
+ getDefaultVersioningConfig,
2990
+ getDefaultValidationConfig,
2991
+ getDefaultLogLevel,
2992
+ getDefaultConflictResolution,
2993
+ getDefaultCacheConfig,
2928
2994
  getConflictsByType,
2929
2995
  getConflictsBySeverity,
2930
2996
  getAllVersions,
@@ -2953,7 +3019,6 @@ export {
2953
3019
  detectConflicts,
2954
3020
  deserializeRuleSet,
2955
3021
  deserializeRule,
2956
- delay,
2957
3022
  date,
2958
3023
  createVersionStore,
2959
3024
  createRuleValidator,
@@ -2965,7 +3030,6 @@ export {
2965
3030
  createEngine,
2966
3031
  createCache,
2967
3032
  conditions,
2968
- compose,
2969
3033
  compareVersions,
2970
3034
  cloneState,
2971
3035
  cloneRule,
@@ -2984,16 +3048,25 @@ export {
2984
3048
  analyzeOperatorUsage,
2985
3049
  analyzeFieldUsage,
2986
3050
  analyzeConsequenceUsage,
2987
- always,
2988
3051
  all,
2989
3052
  addVersion,
2990
3053
  addRules,
2991
3054
  addRuleSet,
2992
3055
  addRule,
3056
+ VersioningConfigSchema,
3057
+ ValidationResultSchema,
3058
+ ValidationOptionsSchema,
3059
+ ValidationErrorSchema,
3060
+ ValidationConfigSchema,
3061
+ RuleStatsSchema,
2993
3062
  RuleSetSchema,
2994
3063
  RuleSchema,
2995
- DEFAULT_VERSIONING_CONFIG,
2996
- DEFAULT_VALIDATION_CONFIG,
2997
- DEFAULT_ENGINE_CONFIG,
2998
- DEFAULT_CACHE_CONFIG
3064
+ LogLevelSchema,
3065
+ EvaluateOptionsSchema,
3066
+ EvaluateConfigSchema,
3067
+ EngineStatsSchema,
3068
+ ConflictResolutionStrategySchema,
3069
+ ConditionGroup as ConditionGroupSchema,
3070
+ CacheStatsSchema,
3071
+ CacheConfigSchema
2999
3072
  };