@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/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,7 @@ 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();
1420
+ const _cacheStats = cache.getStats();
1392
1421
  return {
1393
1422
  totalRules: state.rules.size,
1394
1423
  enabledRules,
@@ -1406,29 +1435,12 @@ var createEngine = (config = {}) => {
1406
1435
  };
1407
1436
  };
1408
1437
  // src/optimizer/index-builder.ts
1409
- import {
1410
- isConditionGroup as isConditionGroup2
1411
- } from "@f-o-t/condition-evaluator";
1412
1438
  var DEFAULT_OPTIONS = {
1413
1439
  indexByField: true,
1414
1440
  indexByTag: true,
1415
1441
  indexByCategory: true,
1416
1442
  indexByPriority: true
1417
1443
  };
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
1444
  var buildIndex = (rules, options = {}) => {
1433
1445
  const opts = { ...DEFAULT_OPTIONS, ...options };
1434
1446
  const byId = new Map;
@@ -1439,7 +1451,7 @@ var buildIndex = (rules, options = {}) => {
1439
1451
  for (const rule2 of rules) {
1440
1452
  byId.set(rule2.id, rule2);
1441
1453
  if (opts.indexByField) {
1442
- const fields = collectFields2(rule2.conditions);
1454
+ const fields = collectConditionFields(rule2.conditions);
1443
1455
  for (const field of fields) {
1444
1456
  const existing = byField.get(field) ?? [];
1445
1457
  existing.push(rule2);
@@ -1544,7 +1556,7 @@ var analyzeOptimizations = (rules) => {
1544
1556
  const suggestions = [];
1545
1557
  const fieldUsage = new Map;
1546
1558
  for (const rule2 of rules) {
1547
- const fields = collectFields2(rule2.conditions);
1559
+ const fields = collectConditionFields(rule2.conditions);
1548
1560
  for (const field of fields) {
1549
1561
  fieldUsage.set(field, (fieldUsage.get(field) ?? 0) + 1);
1550
1562
  }
@@ -1697,6 +1709,8 @@ var importRules = (data, options = {}) => {
1697
1709
  for (let i = 0;i < data.rules.length; i++) {
1698
1710
  try {
1699
1711
  const serialized = data.rules[i];
1712
+ if (!serialized)
1713
+ continue;
1700
1714
  const rule2 = deserializeRule(serialized, options);
1701
1715
  idMapping.set(serialized.id, rule2.id);
1702
1716
  rules.push(rule2);
@@ -1712,6 +1726,8 @@ var importRules = (data, options = {}) => {
1712
1726
  for (let i = 0;i < data.ruleSets.length; i++) {
1713
1727
  try {
1714
1728
  const serialized = data.ruleSets[i];
1729
+ if (!serialized)
1730
+ continue;
1715
1731
  const ruleSet = deserializeRuleSet(serialized, idMapping, options);
1716
1732
  ruleSets.push(ruleSet);
1717
1733
  } catch (error) {
@@ -1723,12 +1739,25 @@ var importRules = (data, options = {}) => {
1723
1739
  }
1724
1740
  }
1725
1741
  }
1742
+ const importedRuleIds = new Set(rules.map((r) => r.id));
1743
+ const orphanedReferences = [];
1744
+ for (const ruleSet of ruleSets) {
1745
+ const missingRuleIds = ruleSet.ruleIds.filter((id) => !importedRuleIds.has(id));
1746
+ if (missingRuleIds.length > 0) {
1747
+ orphanedReferences.push({
1748
+ ruleSetId: ruleSet.id,
1749
+ ruleSetName: ruleSet.name,
1750
+ missingRuleIds
1751
+ });
1752
+ }
1753
+ }
1726
1754
  return {
1727
1755
  success: errors.length === 0,
1728
1756
  rules,
1729
1757
  ruleSets,
1730
1758
  errors,
1731
- idMapping
1759
+ idMapping,
1760
+ orphanedReferences
1732
1761
  };
1733
1762
  };
1734
1763
  var importFromJson = (json, options = {}) => {
@@ -1747,7 +1776,8 @@ var importFromJson = (json, options = {}) => {
1747
1776
  message: `Invalid JSON: ${error instanceof Error ? error.message : String(error)}`
1748
1777
  }
1749
1778
  ],
1750
- idMapping: new Map
1779
+ idMapping: new Map,
1780
+ orphanedReferences: []
1751
1781
  };
1752
1782
  }
1753
1783
  };
@@ -1884,8 +1914,8 @@ var whatIf = (originalRules, modifiedRules, context) => {
1884
1914
  const [ruleId, consequenceType] = key.split(":");
1885
1915
  consequenceChanges.push({
1886
1916
  type: "added",
1887
- consequenceType,
1888
- ruleId
1917
+ consequenceType: consequenceType ?? "",
1918
+ ruleId: ruleId ?? ""
1889
1919
  });
1890
1920
  }
1891
1921
  }
@@ -1894,8 +1924,8 @@ var whatIf = (originalRules, modifiedRules, context) => {
1894
1924
  const [ruleId, consequenceType] = key.split(":");
1895
1925
  consequenceChanges.push({
1896
1926
  type: "removed",
1897
- consequenceType,
1898
- ruleId
1927
+ consequenceType: consequenceType ?? "",
1928
+ ruleId: ruleId ?? ""
1899
1929
  });
1900
1930
  }
1901
1931
  }
@@ -1984,49 +2014,59 @@ var formatSimulationResult = (result) => {
1984
2014
  return lines.join(`
1985
2015
  `);
1986
2016
  };
2017
+ // src/types/evaluation.ts
2018
+ import { z as z3 } from "zod";
2019
+ var ConflictResolutionStrategySchema = z3.enum([
2020
+ "priority",
2021
+ "first-match",
2022
+ "all",
2023
+ "most-specific"
2024
+ ]);
2025
+ var EvaluateOptionsSchema = z3.object({
2026
+ conflictResolution: ConflictResolutionStrategySchema.optional(),
2027
+ maxRules: z3.number().int().positive().optional(),
2028
+ timeout: z3.number().int().positive().optional(),
2029
+ skipDisabled: z3.boolean().optional(),
2030
+ tags: z3.array(z3.string()).optional(),
2031
+ category: z3.string().optional(),
2032
+ ruleSetId: z3.string().optional(),
2033
+ bypassCache: z3.boolean().optional()
2034
+ });
2035
+ var EvaluateConfigSchema = z3.object({
2036
+ conflictResolution: ConflictResolutionStrategySchema,
2037
+ continueOnError: z3.boolean(),
2038
+ collectAllConsequences: z3.boolean()
2039
+ });
1987
2040
  // 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) => {
2041
+ import { z as z4 } from "zod";
2042
+ var RuleSchema = z4.object({
2043
+ id: z4.string().min(1),
2044
+ name: z4.string().min(1),
2045
+ description: z4.string().optional(),
2046
+ conditions: z4.custom((val) => {
1994
2047
  return typeof val === "object" && val !== null && "id" in val && "operator" in val;
1995
2048
  }, "Invalid condition group"),
1996
- consequences: z.array(z.object({
1997
- type: z.string(),
1998
- payload: z.unknown()
2049
+ consequences: z4.array(z4.object({
2050
+ type: z4.string(),
2051
+ payload: z4.unknown()
1999
2052
  })),
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)
2053
+ priority: z4.number().int().default(0),
2054
+ enabled: z4.boolean().default(true),
2055
+ stopOnMatch: z4.boolean().default(false),
2056
+ tags: z4.array(z4.string()).default([]),
2057
+ category: z4.string().optional(),
2058
+ metadata: z4.record(z4.string(), z4.unknown()).optional(),
2059
+ createdAt: z4.date().default(() => new Date),
2060
+ updatedAt: z4.date().default(() => new Date)
2008
2061
  });
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()
2062
+ var RuleSetSchema = z4.object({
2063
+ id: z4.string().min(1),
2064
+ name: z4.string().min(1),
2065
+ description: z4.string().optional(),
2066
+ ruleIds: z4.array(z4.string()),
2067
+ enabled: z4.boolean().default(true),
2068
+ metadata: z4.record(z4.string(), z4.unknown()).optional()
2016
2069
  });
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
2070
  // src/validation/conflicts.ts
2031
2071
  import {
2032
2072
  isConditionGroup as isConditionGroup3
@@ -2038,11 +2078,11 @@ var DEFAULT_OPTIONS2 = {
2038
2078
  checkPriorityCollisions: true,
2039
2079
  checkUnreachableRules: true
2040
2080
  };
2041
- var collectConditionFields = (condition) => {
2081
+ var collectConditionFields2 = (condition) => {
2042
2082
  const fields = new Set;
2043
2083
  if (isConditionGroup3(condition)) {
2044
2084
  for (const child of condition.conditions) {
2045
- const childFields = collectConditionFields(child);
2085
+ const childFields = collectConditionFields2(child);
2046
2086
  for (const field of childFields) {
2047
2087
  fields.add(field);
2048
2088
  }
@@ -2077,8 +2117,8 @@ var getConditionOperatorValues = (condition, field) => {
2077
2117
  return results;
2078
2118
  };
2079
2119
  var areConditionsOverlapping = (conditions1, conditions2) => {
2080
- const fields1 = collectConditionFields(conditions1);
2081
- const fields2 = collectConditionFields(conditions2);
2120
+ const fields1 = collectConditionFields2(conditions1);
2121
+ const fields2 = collectConditionFields2(conditions2);
2082
2122
  const commonFields = new Set([...fields1].filter((f) => fields2.has(f)));
2083
2123
  if (commonFields.size === 0)
2084
2124
  return false;
@@ -2161,6 +2201,8 @@ var findOverlappingConditions = (rules) => {
2161
2201
  for (let j = i + 1;j < rules.length; j++) {
2162
2202
  const rule1 = rules[i];
2163
2203
  const rule2 = rules[j];
2204
+ if (!rule1 || !rule2)
2205
+ continue;
2164
2206
  const key = [rule1.id, rule2.id].sort().join(":");
2165
2207
  if (checked.has(key))
2166
2208
  continue;
@@ -2173,8 +2215,8 @@ var findOverlappingConditions = (rules) => {
2173
2215
  ruleIds: [rule1.id, rule2.id],
2174
2216
  rules: [rule1, rule2],
2175
2217
  details: {
2176
- rule1Fields: [...collectConditionFields(rule1.conditions)],
2177
- rule2Fields: [...collectConditionFields(rule2.conditions)]
2218
+ rule1Fields: [...collectConditionFields2(rule1.conditions)],
2219
+ rule2Fields: [...collectConditionFields2(rule2.conditions)]
2178
2220
  }
2179
2221
  });
2180
2222
  }
@@ -2197,6 +2239,8 @@ var findPriorityCollisions = (rules) => {
2197
2239
  for (let j = i + 1;j < rulesWithPriority.length; j++) {
2198
2240
  const r1 = rulesWithPriority[i];
2199
2241
  const r2 = rulesWithPriority[j];
2242
+ if (!r1 || !r2)
2243
+ continue;
2200
2244
  if (areConditionsOverlapping(r1.conditions, r2.conditions)) {
2201
2245
  overlappingPairs.add(r1.id);
2202
2246
  overlappingPairs.add(r2.id);
@@ -2223,10 +2267,14 @@ var findUnreachableRules = (rules) => {
2223
2267
  const sortedRules = [...rules].sort((a, b) => b.priority - a.priority);
2224
2268
  for (let i = 0;i < sortedRules.length; i++) {
2225
2269
  const rule2 = sortedRules[i];
2270
+ if (!rule2)
2271
+ continue;
2226
2272
  if (!rule2.enabled)
2227
2273
  continue;
2228
2274
  for (let j = 0;j < i; j++) {
2229
2275
  const higherPriorityRule = sortedRules[j];
2276
+ if (!higherPriorityRule)
2277
+ continue;
2230
2278
  if (!higherPriorityRule.enabled || !higherPriorityRule.stopOnMatch) {
2231
2279
  continue;
2232
2280
  }
@@ -2367,7 +2415,7 @@ var checkRuleIntegrity = (rule2, options) => {
2367
2415
  }
2368
2416
  }
2369
2417
  if (options.allowedTags) {
2370
- const invalidTags = rule2.tags.filter((t) => !options.allowedTags.includes(t));
2418
+ const invalidTags = rule2.tags.filter((t) => !options.allowedTags?.includes(t));
2371
2419
  if (invalidTags.length > 0) {
2372
2420
  issues.push(createIssue("INVALID_TAGS", `Rule "${rule2.name}" has invalid tags: ${invalidTags.join(", ")}`, "warning", {
2373
2421
  ruleId: rule2.id,
@@ -2542,11 +2590,22 @@ ${severity.toUpperCase()}S (${issues.length}):`);
2542
2590
  import {
2543
2591
  isConditionGroup as isConditionGroup5
2544
2592
  } from "@f-o-t/condition-evaluator";
2545
- var DEFAULT_OPTIONS4 = {
2546
- validateConditions: true,
2547
- validateConsequences: true,
2548
- strictMode: false
2549
- };
2593
+ import { z as z5 } from "zod";
2594
+ var ValidationErrorSchema = z5.object({
2595
+ path: z5.string(),
2596
+ message: z5.string(),
2597
+ code: z5.string()
2598
+ });
2599
+ var ValidationResultSchema = z5.object({
2600
+ valid: z5.boolean(),
2601
+ errors: z5.array(ValidationErrorSchema)
2602
+ });
2603
+ var ValidationOptionsSchema = z5.object({
2604
+ validateConditions: z5.boolean().optional(),
2605
+ validateConsequences: z5.boolean().optional(),
2606
+ strictMode: z5.boolean().optional()
2607
+ });
2608
+ var DEFAULT_OPTIONS4 = ValidationOptionsSchema.parse({});
2550
2609
  var createError = (path, message, code) => ({
2551
2610
  path,
2552
2611
  message,
@@ -2558,7 +2617,7 @@ var validResult = () => ({
2558
2617
  });
2559
2618
  var invalidResult = (errors) => ({
2560
2619
  valid: false,
2561
- errors
2620
+ errors: [...errors]
2562
2621
  });
2563
2622
  var validateConditionStructure = (condition, path) => {
2564
2623
  const errors = [];
@@ -2599,6 +2658,8 @@ var validateConsequenceStructure = (consequences, consequenceSchemas, strictMode
2599
2658
  const errors = [];
2600
2659
  for (let i = 0;i < consequences.length; i++) {
2601
2660
  const consequence = consequences[i];
2661
+ if (!consequence)
2662
+ continue;
2602
2663
  const path = `consequences[${i}]`;
2603
2664
  if (!consequence.type || typeof consequence.type !== "string") {
2604
2665
  errors.push(createError(`${path}.type`, "Consequence must have a type", "MISSING_CONSEQUENCE_TYPE"));
@@ -2635,8 +2696,8 @@ var validateRule = (rule2, options = {}) => {
2635
2696
  const conditionErrors = validateConditionStructure(validRule.conditions, "conditions");
2636
2697
  errors.push(...conditionErrors);
2637
2698
  }
2638
- if (opts.validateConsequences) {
2639
- const consequenceErrors = validateConsequenceStructure(validRule.consequences, opts.consequenceSchemas, opts.strictMode);
2699
+ if (opts.validateConsequences || opts.strictMode) {
2700
+ const consequenceErrors = validateConsequenceStructure(validRule.consequences, opts.consequenceSchemas, opts.strictMode ?? false);
2640
2701
  errors.push(...consequenceErrors);
2641
2702
  }
2642
2703
  return errors.length > 0 ? invalidResult(errors) : validResult();
@@ -2867,7 +2928,6 @@ export {
2867
2928
  validateRule,
2868
2929
  validateConditions,
2869
2930
  updateRule,
2870
- tap,
2871
2931
  str,
2872
2932
  sortRules,
2873
2933
  sortByUpdatedAt,
@@ -2885,16 +2945,18 @@ export {
2885
2945
  removeRuleSet,
2886
2946
  removeRule,
2887
2947
  pruneOldVersions,
2888
- pipe,
2948
+ parseVersioningConfig,
2949
+ parseValidationConfig,
2889
2950
  parseRule,
2951
+ parseCacheConfig,
2890
2952
  or,
2891
2953
  num,
2892
2954
  mergeRuleSets,
2893
2955
  measureTimeAsync,
2894
2956
  measureTime,
2957
+ isConditionGroup6 as isConditionGroup,
2895
2958
  importRules,
2896
2959
  importFromJson,
2897
- identity,
2898
2960
  hashRules,
2899
2961
  hashContext,
2900
2962
  hasErrors,
@@ -2925,6 +2987,11 @@ export {
2925
2987
  getLatestVersion,
2926
2988
  getIndexStats,
2927
2989
  getHistory,
2990
+ getDefaultVersioningConfig,
2991
+ getDefaultValidationConfig,
2992
+ getDefaultLogLevel,
2993
+ getDefaultConflictResolution,
2994
+ getDefaultCacheConfig,
2928
2995
  getConflictsByType,
2929
2996
  getConflictsBySeverity,
2930
2997
  getAllVersions,
@@ -2953,7 +3020,6 @@ export {
2953
3020
  detectConflicts,
2954
3021
  deserializeRuleSet,
2955
3022
  deserializeRule,
2956
- delay,
2957
3023
  date,
2958
3024
  createVersionStore,
2959
3025
  createRuleValidator,
@@ -2965,7 +3031,6 @@ export {
2965
3031
  createEngine,
2966
3032
  createCache,
2967
3033
  conditions,
2968
- compose,
2969
3034
  compareVersions,
2970
3035
  cloneState,
2971
3036
  cloneRule,
@@ -2984,16 +3049,25 @@ export {
2984
3049
  analyzeOperatorUsage,
2985
3050
  analyzeFieldUsage,
2986
3051
  analyzeConsequenceUsage,
2987
- always,
2988
3052
  all,
2989
3053
  addVersion,
2990
3054
  addRules,
2991
3055
  addRuleSet,
2992
3056
  addRule,
3057
+ VersioningConfigSchema,
3058
+ ValidationResultSchema,
3059
+ ValidationOptionsSchema,
3060
+ ValidationErrorSchema,
3061
+ ValidationConfigSchema,
3062
+ RuleStatsSchema,
2993
3063
  RuleSetSchema,
2994
3064
  RuleSchema,
2995
- DEFAULT_VERSIONING_CONFIG,
2996
- DEFAULT_VALIDATION_CONFIG,
2997
- DEFAULT_ENGINE_CONFIG,
2998
- DEFAULT_CACHE_CONFIG
3065
+ LogLevelSchema,
3066
+ EvaluateOptionsSchema,
3067
+ EvaluateConfigSchema,
3068
+ EngineStatsSchema,
3069
+ ConflictResolutionStrategySchema,
3070
+ ConditionGroup as ConditionGroupSchema,
3071
+ CacheStatsSchema,
3072
+ CacheConfigSchema
2999
3073
  };