@f-o-t/rules-engine 1.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 ADDED
@@ -0,0 +1,2999 @@
1
+ // src/analyzer/analysis.ts
2
+ import {
3
+ isConditionGroup
4
+ } from "@f-o-t/condition-evaluator";
5
+ var countConditions = (condition) => {
6
+ if (isConditionGroup(condition)) {
7
+ return condition.conditions.reduce((sum, c) => sum + countConditions(c), 0);
8
+ }
9
+ return 1;
10
+ };
11
+ var calculateMaxDepth = (condition, currentDepth = 1) => {
12
+ if (isConditionGroup(condition)) {
13
+ if (condition.conditions.length === 0)
14
+ return currentDepth;
15
+ return Math.max(...condition.conditions.map((c) => calculateMaxDepth(c, currentDepth + 1)));
16
+ }
17
+ return currentDepth;
18
+ };
19
+ var countGroups = (condition) => {
20
+ if (isConditionGroup(condition)) {
21
+ return 1 + condition.conditions.reduce((sum, c) => sum + countGroups(c), 0);
22
+ }
23
+ return 0;
24
+ };
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
+ };
39
+ var collectOperators = (condition) => {
40
+ const operators = new Set;
41
+ const traverse = (c) => {
42
+ if (isConditionGroup(c)) {
43
+ operators.add(c.operator);
44
+ for (const child of c.conditions) {
45
+ traverse(child);
46
+ }
47
+ } else {
48
+ operators.add(c.operator);
49
+ }
50
+ };
51
+ traverse(condition);
52
+ return operators;
53
+ };
54
+ var calculateComplexityScore = (totalConditions, maxDepth, groupCount, uniqueFields) => {
55
+ return totalConditions * 1 + maxDepth * 2 + groupCount * 1.5 + uniqueFields * 0.5;
56
+ };
57
+ var analyzeRuleComplexity = (rule) => {
58
+ const totalConditions = countConditions(rule.conditions);
59
+ const maxDepth = calculateMaxDepth(rule.conditions);
60
+ const groupCount = countGroups(rule.conditions);
61
+ const uniqueFields = collectFields(rule.conditions).size;
62
+ const uniqueOperators = collectOperators(rule.conditions).size;
63
+ const consequenceCount = rule.consequences.length;
64
+ const complexityScore = calculateComplexityScore(totalConditions, maxDepth, groupCount, uniqueFields);
65
+ return {
66
+ ruleId: rule.id,
67
+ ruleName: rule.name,
68
+ totalConditions,
69
+ maxDepth,
70
+ groupCount,
71
+ uniqueFields,
72
+ uniqueOperators,
73
+ consequenceCount,
74
+ complexityScore
75
+ };
76
+ };
77
+ var analyzeRuleSet = (rules) => {
78
+ const complexities = rules.map(analyzeRuleComplexity);
79
+ const allFields = new Set;
80
+ const allOperators = new Set;
81
+ const allConsequenceTypes = new Set;
82
+ const allCategories = new Set;
83
+ const allTags = new Set;
84
+ let totalConditions = 0;
85
+ let totalConsequences = 0;
86
+ let minPriority = Number.POSITIVE_INFINITY;
87
+ let maxPriority = Number.NEGATIVE_INFINITY;
88
+ let enabledCount = 0;
89
+ for (const rule of rules) {
90
+ totalConditions += countConditions(rule.conditions);
91
+ totalConsequences += rule.consequences.length;
92
+ if (rule.priority < minPriority)
93
+ minPriority = rule.priority;
94
+ if (rule.priority > maxPriority)
95
+ maxPriority = rule.priority;
96
+ if (rule.enabled)
97
+ enabledCount++;
98
+ for (const field of collectFields(rule.conditions)) {
99
+ allFields.add(field);
100
+ }
101
+ for (const operator of collectOperators(rule.conditions)) {
102
+ allOperators.add(operator);
103
+ }
104
+ for (const consequence of rule.consequences) {
105
+ allConsequenceTypes.add(consequence.type);
106
+ }
107
+ if (rule.category)
108
+ allCategories.add(rule.category);
109
+ for (const tag of rule.tags)
110
+ allTags.add(tag);
111
+ }
112
+ const averageComplexity = complexities.length > 0 ? complexities.reduce((sum, c) => sum + c.complexityScore, 0) / complexities.length : 0;
113
+ const complexityDistribution = {
114
+ low: complexities.filter((c) => c.complexityScore < 5).length,
115
+ medium: complexities.filter((c) => c.complexityScore >= 5 && c.complexityScore < 15).length,
116
+ high: complexities.filter((c) => c.complexityScore >= 15).length
117
+ };
118
+ return {
119
+ ruleCount: rules.length,
120
+ enabledCount,
121
+ disabledCount: rules.length - enabledCount,
122
+ totalConditions,
123
+ totalConsequences,
124
+ uniqueFields: [...allFields].sort(),
125
+ uniqueOperators: [...allOperators].sort(),
126
+ uniqueConsequenceTypes: [...allConsequenceTypes].sort(),
127
+ uniqueCategories: [...allCategories].sort(),
128
+ uniqueTags: [...allTags].sort(),
129
+ priorityRange: {
130
+ min: minPriority === Number.POSITIVE_INFINITY ? 0 : minPriority,
131
+ max: maxPriority === Number.NEGATIVE_INFINITY ? 0 : maxPriority
132
+ },
133
+ averageComplexity,
134
+ complexityDistribution,
135
+ ruleComplexities: complexities
136
+ };
137
+ };
138
+ var analyzeFieldUsage = (rules) => {
139
+ const fieldMap = new Map;
140
+ for (const rule of rules) {
141
+ const traverse = (c) => {
142
+ if (isConditionGroup(c)) {
143
+ for (const child of c.conditions) {
144
+ traverse(child);
145
+ }
146
+ } else {
147
+ const existing = fieldMap.get(c.field) ?? {
148
+ types: new Set,
149
+ operators: new Set,
150
+ rules: []
151
+ };
152
+ existing.types.add(c.type);
153
+ existing.operators.add(c.operator);
154
+ if (!existing.rules.some((r) => r.id === rule.id)) {
155
+ existing.rules.push({ id: rule.id, name: rule.name });
156
+ }
157
+ fieldMap.set(c.field, existing);
158
+ }
159
+ };
160
+ traverse(rule.conditions);
161
+ }
162
+ return [...fieldMap.entries()].map(([field, data]) => ({
163
+ field,
164
+ count: data.rules.length,
165
+ types: [...data.types].sort(),
166
+ operators: [...data.operators].sort(),
167
+ rules: data.rules
168
+ })).sort((a, b) => b.count - a.count);
169
+ };
170
+ var analyzeOperatorUsage = (rules) => {
171
+ const operatorMap = new Map;
172
+ for (const rule of rules) {
173
+ const traverse = (c) => {
174
+ if (isConditionGroup(c)) {
175
+ for (const child of c.conditions) {
176
+ traverse(child);
177
+ }
178
+ } else {
179
+ const key = `${c.type}:${c.operator}`;
180
+ const existing = operatorMap.get(key) ?? {
181
+ type: c.type,
182
+ rules: []
183
+ };
184
+ if (!existing.rules.some((r) => r.id === rule.id)) {
185
+ existing.rules.push({ id: rule.id, name: rule.name });
186
+ }
187
+ operatorMap.set(key, existing);
188
+ }
189
+ };
190
+ traverse(rule.conditions);
191
+ }
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);
198
+ };
199
+ var analyzeConsequenceUsage = (rules) => {
200
+ const consequenceMap = new Map;
201
+ for (const rule of rules) {
202
+ for (const consequence of rule.consequences) {
203
+ const type = consequence.type;
204
+ const existing = consequenceMap.get(type) ?? [];
205
+ if (!existing.some((r) => r.id === rule.id)) {
206
+ existing.push({ id: rule.id, name: rule.name });
207
+ }
208
+ consequenceMap.set(type, existing);
209
+ }
210
+ }
211
+ return [...consequenceMap.entries()].map(([type, rules2]) => ({
212
+ type,
213
+ count: rules2.length,
214
+ rules: rules2
215
+ })).sort((a, b) => b.count - a.count);
216
+ };
217
+ var findMostComplexRules = (rules, limit = 10) => {
218
+ return rules.map(analyzeRuleComplexity).sort((a, b) => b.complexityScore - a.complexityScore).slice(0, limit);
219
+ };
220
+ var findLeastUsedFields = (rules, limit = 10) => {
221
+ return [...analyzeFieldUsage(rules)].sort((a, b) => a.count - b.count).slice(0, limit);
222
+ };
223
+ var formatRuleSetAnalysis = (analysis) => {
224
+ const lines = [
225
+ "=== Rule Set Analysis ===",
226
+ "",
227
+ `Rules: ${analysis.ruleCount} (${analysis.enabledCount} enabled, ${analysis.disabledCount} disabled)`,
228
+ `Total Conditions: ${analysis.totalConditions}`,
229
+ `Total Consequences: ${analysis.totalConsequences}`,
230
+ "",
231
+ `Unique Fields: ${analysis.uniqueFields.length}`,
232
+ ` ${analysis.uniqueFields.join(", ") || "(none)"}`,
233
+ "",
234
+ `Unique Operators: ${analysis.uniqueOperators.length}`,
235
+ ` ${analysis.uniqueOperators.join(", ") || "(none)"}`,
236
+ "",
237
+ `Consequence Types: ${analysis.uniqueConsequenceTypes.length}`,
238
+ ` ${analysis.uniqueConsequenceTypes.join(", ") || "(none)"}`,
239
+ "",
240
+ `Categories: ${analysis.uniqueCategories.length}`,
241
+ ` ${analysis.uniqueCategories.join(", ") || "(none)"}`,
242
+ "",
243
+ `Tags: ${analysis.uniqueTags.length}`,
244
+ ` ${analysis.uniqueTags.join(", ") || "(none)"}`,
245
+ "",
246
+ `Priority Range: ${analysis.priorityRange.min} - ${analysis.priorityRange.max}`,
247
+ "",
248
+ `Average Complexity: ${analysis.averageComplexity.toFixed(2)}`,
249
+ `Complexity Distribution:`,
250
+ ` Low (< 5): ${analysis.complexityDistribution.low}`,
251
+ ` Medium (5-15): ${analysis.complexityDistribution.medium}`,
252
+ ` High (> 15): ${analysis.complexityDistribution.high}`
253
+ ];
254
+ return lines.join(`
255
+ `);
256
+ };
257
+ // src/builder/conditions.ts
258
+ var conditionIdCounter = 0;
259
+ var groupIdCounter = 0;
260
+ var generateConditionId = () => `cond-${++conditionIdCounter}`;
261
+ var generateGroupId = () => `group-${++groupIdCounter}`;
262
+ var resetBuilderIds = () => {
263
+ conditionIdCounter = 0;
264
+ groupIdCounter = 0;
265
+ };
266
+ var createConditionBuilder = (state = { conditions: [] }, operator = "AND") => {
267
+ const addCondition = (condition) => {
268
+ return createConditionBuilder({ conditions: [...state.conditions, condition] }, operator);
269
+ };
270
+ return {
271
+ number: (field, op, value) => addCondition({
272
+ id: generateConditionId(),
273
+ type: "number",
274
+ field,
275
+ operator: op,
276
+ value
277
+ }),
278
+ string: (field, op, value) => addCondition({
279
+ id: generateConditionId(),
280
+ type: "string",
281
+ field,
282
+ operator: op,
283
+ value
284
+ }),
285
+ boolean: (field, op, value) => addCondition({
286
+ id: generateConditionId(),
287
+ type: "boolean",
288
+ field,
289
+ operator: op,
290
+ value
291
+ }),
292
+ date: (field, op, value) => {
293
+ const normalizedValue = value instanceof Date ? value.toISOString() : Array.isArray(value) ? value.map((v) => v instanceof Date ? v.toISOString() : v) : value;
294
+ return addCondition({
295
+ id: generateConditionId(),
296
+ type: "date",
297
+ field,
298
+ operator: op,
299
+ value: normalizedValue
300
+ });
301
+ },
302
+ array: (field, op, value) => addCondition({
303
+ id: generateConditionId(),
304
+ type: "array",
305
+ field,
306
+ operator: op,
307
+ value
308
+ }),
309
+ ref: (field, op, valueRef) => addCondition({
310
+ id: generateConditionId(),
311
+ type: "number",
312
+ field,
313
+ operator: op,
314
+ valueRef
315
+ }),
316
+ and: (builderFn) => {
317
+ const nestedBuilder = createConditionBuilder({ conditions: [] }, "AND");
318
+ const result = builderFn(nestedBuilder);
319
+ return addCondition(result.build());
320
+ },
321
+ or: (builderFn) => {
322
+ const nestedBuilder = createConditionBuilder({ conditions: [] }, "OR");
323
+ const result = builderFn(nestedBuilder);
324
+ return addCondition(result.build());
325
+ },
326
+ raw: (condition) => addCondition(condition),
327
+ build: () => ({
328
+ id: generateGroupId(),
329
+ operator,
330
+ conditions: state.conditions
331
+ }),
332
+ getConditions: () => state.conditions
333
+ };
334
+ };
335
+ var conditions = () => createConditionBuilder({ conditions: [] }, "AND");
336
+ var and = (builderFn) => {
337
+ const builder = createConditionBuilder({ conditions: [] }, "AND");
338
+ return builderFn(builder).build();
339
+ };
340
+ var or = (builderFn) => {
341
+ const builder = createConditionBuilder({ conditions: [] }, "OR");
342
+ return builderFn(builder).build();
343
+ };
344
+ var all = (...items) => ({
345
+ id: generateGroupId(),
346
+ operator: "AND",
347
+ conditions: items
348
+ });
349
+ var any = (...items) => ({
350
+ id: generateGroupId(),
351
+ operator: "OR",
352
+ conditions: items
353
+ });
354
+ var num = (field, operator, value) => ({
355
+ id: generateConditionId(),
356
+ type: "number",
357
+ field,
358
+ operator,
359
+ value
360
+ });
361
+ var str = (field, operator, value) => ({
362
+ id: generateConditionId(),
363
+ type: "string",
364
+ field,
365
+ operator,
366
+ value
367
+ });
368
+ var bool = (field, operator, value) => ({
369
+ id: generateConditionId(),
370
+ type: "boolean",
371
+ field,
372
+ operator,
373
+ value
374
+ });
375
+ var date = (field, operator, value) => {
376
+ const normalizedValue = value instanceof Date ? value.toISOString() : Array.isArray(value) ? value.map((v) => v instanceof Date ? v.toISOString() : v) : value;
377
+ return {
378
+ id: generateConditionId(),
379
+ type: "date",
380
+ field,
381
+ operator,
382
+ value: normalizedValue
383
+ };
384
+ };
385
+ var arr = (field, operator, value) => ({
386
+ id: generateConditionId(),
387
+ type: "array",
388
+ field,
389
+ operator,
390
+ value
391
+ });
392
+ // src/builder/rule.ts
393
+ var createRuleBuilder = (state = {
394
+ consequences: [],
395
+ priority: 0,
396
+ enabled: true,
397
+ stopOnMatch: false,
398
+ tags: []
399
+ }) => {
400
+ const update = (updates) => {
401
+ return createRuleBuilder({ ...state, ...updates });
402
+ };
403
+ return {
404
+ id: (id) => update({ id }),
405
+ named: (name) => update({ name }),
406
+ describedAs: (description) => update({ description }),
407
+ when: (conditionsOrBuilder) => {
408
+ if (typeof conditionsOrBuilder === "function") {
409
+ const builder = conditions();
410
+ const result = conditionsOrBuilder(builder);
411
+ return update({ conditions: result.build() });
412
+ }
413
+ return update({ conditions: conditionsOrBuilder });
414
+ },
415
+ then: (type, payload) => {
416
+ return update({
417
+ consequences: [
418
+ ...state.consequences,
419
+ { type, payload }
420
+ ]
421
+ });
422
+ },
423
+ withPriority: (priority) => update({ priority }),
424
+ enabled: (enabled = true) => update({ enabled }),
425
+ disabled: () => update({ enabled: false }),
426
+ stopOnMatch: (stop = true) => update({ stopOnMatch: stop }),
427
+ tagged: (...tags) => update({ tags: [...state.tags, ...tags] }),
428
+ inCategory: (category) => update({ category }),
429
+ withMetadata: (metadata) => update({ metadata: { ...state.metadata, ...metadata } }),
430
+ build: () => {
431
+ if (!state.name) {
432
+ throw new Error("Rule must have a name");
433
+ }
434
+ if (!state.conditions) {
435
+ throw new Error("Rule must have conditions");
436
+ }
437
+ if (state.consequences.length === 0) {
438
+ throw new Error("Rule must have at least one consequence");
439
+ }
440
+ return {
441
+ id: state.id,
442
+ name: state.name,
443
+ description: state.description,
444
+ conditions: state.conditions,
445
+ consequences: state.consequences,
446
+ priority: state.priority,
447
+ enabled: state.enabled,
448
+ stopOnMatch: state.stopOnMatch,
449
+ tags: state.tags,
450
+ category: state.category,
451
+ metadata: state.metadata
452
+ };
453
+ },
454
+ getState: () => state
455
+ };
456
+ };
457
+ var rule = () => {
458
+ return createRuleBuilder();
459
+ };
460
+ var createRule = (builderFn) => {
461
+ const builder = createRuleBuilder();
462
+ return builderFn(builder).build();
463
+ };
464
+ // src/cache/cache.ts
465
+ var createCache = (options) => {
466
+ const entries = new Map;
467
+ let hits = 0;
468
+ let misses = 0;
469
+ let evictions = 0;
470
+ const isExpired = (entry) => {
471
+ return Date.now() > entry.expiresAt;
472
+ };
473
+ const evictExpired = () => {
474
+ const now = Date.now();
475
+ for (const [key, entry] of entries) {
476
+ if (now > entry.expiresAt) {
477
+ entries.delete(key);
478
+ evictions++;
479
+ options.onEvict?.(key, entry.value);
480
+ }
481
+ }
482
+ };
483
+ const evictOldest = () => {
484
+ if (entries.size === 0)
485
+ 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) {
495
+ const entry = entries.get(oldestKey);
496
+ entries.delete(oldestKey);
497
+ evictions++;
498
+ if (entry) {
499
+ options.onEvict?.(oldestKey, entry.value);
500
+ }
501
+ }
502
+ };
503
+ const get = (key) => {
504
+ const entry = entries.get(key);
505
+ if (!entry) {
506
+ misses++;
507
+ return;
508
+ }
509
+ if (isExpired(entry)) {
510
+ entries.delete(key);
511
+ evictions++;
512
+ options.onEvict?.(key, entry.value);
513
+ misses++;
514
+ return;
515
+ }
516
+ hits++;
517
+ return entry.value;
518
+ };
519
+ const set = (key, value) => {
520
+ evictExpired();
521
+ while (entries.size >= options.maxSize) {
522
+ evictOldest();
523
+ }
524
+ const now = Date.now();
525
+ entries.set(key, {
526
+ value,
527
+ expiresAt: now + options.ttl,
528
+ createdAt: now
529
+ });
530
+ };
531
+ const has = (key) => {
532
+ const entry = entries.get(key);
533
+ if (!entry)
534
+ return false;
535
+ if (isExpired(entry)) {
536
+ entries.delete(key);
537
+ evictions++;
538
+ options.onEvict?.(key, entry.value);
539
+ return false;
540
+ }
541
+ return true;
542
+ };
543
+ const deleteEntry = (key) => {
544
+ return entries.delete(key);
545
+ };
546
+ const clear = () => {
547
+ entries.clear();
548
+ };
549
+ const getStats = () => {
550
+ const totalRequests = hits + misses;
551
+ return {
552
+ size: entries.size,
553
+ maxSize: options.maxSize,
554
+ hits,
555
+ misses,
556
+ hitRate: totalRequests > 0 ? hits / totalRequests : 0,
557
+ evictions
558
+ };
559
+ };
560
+ return {
561
+ get,
562
+ set,
563
+ has,
564
+ delete: deleteEntry,
565
+ clear,
566
+ getStats
567
+ };
568
+ };
569
+ // src/cache/noop.ts
570
+ var createNoopCache = () => {
571
+ return {
572
+ get: () => {
573
+ return;
574
+ },
575
+ set: () => {},
576
+ has: () => false,
577
+ delete: () => false,
578
+ clear: () => {},
579
+ getStats: () => ({
580
+ size: 0,
581
+ maxSize: 0,
582
+ hits: 0,
583
+ misses: 0,
584
+ hitRate: 0,
585
+ evictions: 0
586
+ })
587
+ };
588
+ };
589
+ // src/core/evaluate.ts
590
+ import {
591
+ evaluateConditionGroup
592
+ } from "@f-o-t/condition-evaluator";
593
+
594
+ // src/utils/time.ts
595
+ var measureTime = (fn) => {
596
+ const start = performance.now();
597
+ const result = fn();
598
+ const durationMs = performance.now() - start;
599
+ return { result, durationMs };
600
+ };
601
+ var measureTimeAsync = async (fn) => {
602
+ const start = performance.now();
603
+ const result = await fn();
604
+ const durationMs = performance.now() - start;
605
+ return { result, durationMs };
606
+ };
607
+ var withTimeout = (promise, timeoutMs, errorMessage = "Operation timed out") => {
608
+ return new Promise((resolve, reject) => {
609
+ const timer = setTimeout(() => {
610
+ reject(new Error(errorMessage));
611
+ }, timeoutMs);
612
+ promise.then((result) => {
613
+ clearTimeout(timer);
614
+ resolve(result);
615
+ }).catch((error) => {
616
+ clearTimeout(timer);
617
+ reject(error);
618
+ });
619
+ });
620
+ };
621
+ var delay = (ms) => {
622
+ return new Promise((resolve) => setTimeout(resolve, ms));
623
+ };
624
+
625
+ // src/core/evaluate.ts
626
+ var evaluateRule = (rule2, context, options = {}) => {
627
+ if (options.skipDisabled && !rule2.enabled) {
628
+ return {
629
+ ruleId: rule2.id,
630
+ ruleName: rule2.name,
631
+ matched: false,
632
+ conditionResult: createEmptyGroupResult(rule2.conditions.id),
633
+ consequences: [],
634
+ evaluationTimeMs: 0,
635
+ skipped: true,
636
+ skipReason: "Rule is disabled"
637
+ };
638
+ }
639
+ const { result: conditionResult, durationMs } = measureTime(() => {
640
+ try {
641
+ const evalContext = {
642
+ data: context.data,
643
+ metadata: context.metadata
644
+ };
645
+ return evaluateConditionGroup(rule2.conditions, evalContext);
646
+ } catch (error) {
647
+ return {
648
+ error,
649
+ result: createEmptyGroupResult(rule2.conditions.id)
650
+ };
651
+ }
652
+ });
653
+ if ("error" in conditionResult) {
654
+ return {
655
+ ruleId: rule2.id,
656
+ ruleName: rule2.name,
657
+ matched: false,
658
+ conditionResult: conditionResult.result,
659
+ consequences: [],
660
+ evaluationTimeMs: durationMs,
661
+ skipped: false,
662
+ error: conditionResult.error instanceof Error ? conditionResult.error : new Error(String(conditionResult.error))
663
+ };
664
+ }
665
+ const matched = conditionResult.passed;
666
+ const consequences = matched ? rule2.consequences.map((consequence) => ({
667
+ type: consequence.type,
668
+ payload: consequence.payload,
669
+ ruleId: rule2.id,
670
+ ruleName: rule2.name,
671
+ priority: rule2.priority
672
+ })) : [];
673
+ return {
674
+ ruleId: rule2.id,
675
+ ruleName: rule2.name,
676
+ matched,
677
+ conditionResult,
678
+ consequences,
679
+ evaluationTimeMs: durationMs,
680
+ skipped: false
681
+ };
682
+ };
683
+ var createEmptyGroupResult = (groupId) => ({
684
+ groupId,
685
+ operator: "AND",
686
+ passed: false,
687
+ results: []
688
+ });
689
+ var evaluateRules = (rules, context, options = {}) => {
690
+ const config = {
691
+ conflictResolution: options.config?.conflictResolution ?? "priority",
692
+ continueOnError: options.config?.continueOnError ?? true,
693
+ collectAllConsequences: options.config?.collectAllConsequences ?? true
694
+ };
695
+ const results = [];
696
+ const matchedRules = [];
697
+ const consequences = [];
698
+ let stoppedEarly = false;
699
+ let stoppedByRuleId;
700
+ for (const rule2 of rules) {
701
+ const result = evaluateRule(rule2, context, { skipDisabled: true });
702
+ results.push(result);
703
+ options.onRuleEvaluated?.(result);
704
+ if (result.error && !config.continueOnError) {
705
+ break;
706
+ }
707
+ if (result.matched) {
708
+ matchedRules.push(rule2);
709
+ consequences.push(...result.consequences);
710
+ if (rule2.stopOnMatch) {
711
+ stoppedEarly = true;
712
+ stoppedByRuleId = rule2.id;
713
+ break;
714
+ }
715
+ if (config.conflictResolution === "first-match") {
716
+ stoppedEarly = true;
717
+ stoppedByRuleId = rule2.id;
718
+ break;
719
+ }
720
+ }
721
+ }
722
+ return {
723
+ results,
724
+ matchedRules,
725
+ consequences,
726
+ stoppedEarly,
727
+ stoppedByRuleId
728
+ };
729
+ };
730
+ // src/core/filter.ts
731
+ var filterRules = (filters) => {
732
+ return (rules) => {
733
+ return rules.filter((rule2) => {
734
+ if (filters.enabled !== undefined && rule2.enabled !== filters.enabled) {
735
+ return false;
736
+ }
737
+ if (filters.tags && filters.tags.length > 0) {
738
+ const hasMatchingTag = filters.tags.some((tag) => rule2.tags.includes(tag));
739
+ if (!hasMatchingTag) {
740
+ return false;
741
+ }
742
+ }
743
+ if (filters.category !== undefined && rule2.category !== filters.category) {
744
+ return false;
745
+ }
746
+ if (filters.ids && filters.ids.length > 0) {
747
+ if (!filters.ids.includes(rule2.id)) {
748
+ return false;
749
+ }
750
+ }
751
+ return true;
752
+ });
753
+ };
754
+ };
755
+ var filterByEnabled = (enabled) => {
756
+ return filterRules({ enabled });
757
+ };
758
+ var filterByTags = (tags) => {
759
+ return filterRules({ tags });
760
+ };
761
+ var filterByCategory = (category) => {
762
+ return filterRules({ category });
763
+ };
764
+ var filterByIds = (ids) => {
765
+ return filterRules({ ids });
766
+ };
767
+ // src/core/group.ts
768
+ var groupRules = (field) => {
769
+ return (rules) => {
770
+ const groups = new Map;
771
+ for (const rule2 of rules) {
772
+ let key;
773
+ switch (field) {
774
+ case "category":
775
+ key = rule2.category ?? "uncategorized";
776
+ break;
777
+ case "priority":
778
+ key = rule2.priority;
779
+ break;
780
+ case "enabled":
781
+ key = rule2.enabled;
782
+ break;
783
+ }
784
+ const existing = groups.get(key);
785
+ if (existing) {
786
+ existing.push(rule2);
787
+ } else {
788
+ groups.set(key, [rule2]);
789
+ }
790
+ }
791
+ return groups;
792
+ };
793
+ };
794
+ var groupByCategory = () => {
795
+ return groupRules("category");
796
+ };
797
+ var groupByPriority = () => {
798
+ return groupRules("priority");
799
+ };
800
+ var groupByEnabled = () => {
801
+ return groupRules("enabled");
802
+ };
803
+ var groupByCustom = (keyFn) => {
804
+ return (rules) => {
805
+ const groups = new Map;
806
+ for (const rule2 of rules) {
807
+ const key = keyFn(rule2);
808
+ const existing = groups.get(key);
809
+ if (existing) {
810
+ existing.push(rule2);
811
+ } else {
812
+ groups.set(key, [rule2]);
813
+ }
814
+ }
815
+ return groups;
816
+ };
817
+ };
818
+ // src/core/sort.ts
819
+ var sortRules = (options) => {
820
+ const normalizedOptions = typeof options === "string" ? { field: options, direction: "desc" } : options;
821
+ const { field, direction = "desc" } = normalizedOptions;
822
+ return (rules) => {
823
+ const sorted = [...rules].sort((a, b) => {
824
+ let comparison = 0;
825
+ switch (field) {
826
+ case "priority":
827
+ comparison = a.priority - b.priority;
828
+ break;
829
+ case "name":
830
+ comparison = a.name.localeCompare(b.name);
831
+ break;
832
+ case "createdAt":
833
+ comparison = a.createdAt.getTime() - b.createdAt.getTime();
834
+ break;
835
+ case "updatedAt":
836
+ comparison = a.updatedAt.getTime() - b.updatedAt.getTime();
837
+ break;
838
+ }
839
+ return direction === "desc" ? -comparison : comparison;
840
+ });
841
+ return sorted;
842
+ };
843
+ };
844
+ var sortByPriority = (direction = "desc") => {
845
+ return sortRules({ field: "priority", direction });
846
+ };
847
+ var sortByName = (direction = "asc") => {
848
+ return sortRules({ field: "name", direction });
849
+ };
850
+ var sortByCreatedAt = (direction = "desc") => {
851
+ return sortRules({ field: "createdAt", direction });
852
+ };
853
+ var sortByUpdatedAt = (direction = "desc") => {
854
+ return sortRules({ field: "updatedAt", direction });
855
+ };
856
+ // 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
+ };
879
+
880
+ // src/types/state.ts
881
+ var createInitialState = () => ({
882
+ rules: new Map,
883
+ ruleSets: new Map,
884
+ ruleOrder: []
885
+ });
886
+ var createInitialOptimizerState = () => ({
887
+ ruleStats: new Map,
888
+ lastOptimized: undefined
889
+ });
890
+ var createInitialRuleStats = () => ({
891
+ evaluations: 0,
892
+ matches: 0,
893
+ errors: 0,
894
+ totalTimeMs: 0,
895
+ avgTimeMs: 0,
896
+ lastEvaluated: undefined
897
+ });
898
+
899
+ // src/utils/hash.ts
900
+ var hashContext = (context) => {
901
+ const str2 = JSON.stringify(context, (_, value) => {
902
+ if (value instanceof Date) {
903
+ return value.toISOString();
904
+ }
905
+ if (value instanceof Map) {
906
+ return Object.fromEntries(value);
907
+ }
908
+ if (value instanceof Set) {
909
+ return Array.from(value);
910
+ }
911
+ return value;
912
+ });
913
+ if (typeof Bun !== "undefined" && Bun.hash) {
914
+ return Bun.hash(str2).toString(16);
915
+ }
916
+ let hash = 0;
917
+ for (let i = 0;i < str2.length; i++) {
918
+ const char = str2.charCodeAt(i);
919
+ hash = (hash << 5) - hash + char;
920
+ hash = hash & hash;
921
+ }
922
+ return Math.abs(hash).toString(16);
923
+ };
924
+ var hashRules = (ruleIds) => {
925
+ return hashContext(ruleIds.slice().sort());
926
+ };
927
+
928
+ // src/utils/id.ts
929
+ var generateId = () => {
930
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
931
+ return crypto.randomUUID();
932
+ }
933
+ return `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 11)}`;
934
+ };
935
+
936
+ // src/engine/hooks.ts
937
+ var executeBeforeEvaluation = async (hooks, context, rules) => {
938
+ if (!hooks.beforeEvaluation)
939
+ return;
940
+ try {
941
+ await hooks.beforeEvaluation(context, rules);
942
+ } catch {}
943
+ };
944
+ var executeAfterEvaluation = async (hooks, result) => {
945
+ if (!hooks.afterEvaluation)
946
+ return;
947
+ try {
948
+ await hooks.afterEvaluation(result);
949
+ } catch {}
950
+ };
951
+ var executeBeforeRuleEvaluation = async (hooks, rule2, context) => {
952
+ if (!hooks.beforeRuleEvaluation)
953
+ return;
954
+ try {
955
+ await hooks.beforeRuleEvaluation(rule2, context);
956
+ } catch {}
957
+ };
958
+ var executeAfterRuleEvaluation = async (hooks, rule2, result) => {
959
+ if (!hooks.afterRuleEvaluation)
960
+ return;
961
+ try {
962
+ await hooks.afterRuleEvaluation(rule2, result);
963
+ } catch {}
964
+ };
965
+ var executeOnRuleMatch = async (hooks, rule2, context) => {
966
+ if (!hooks.onRuleMatch)
967
+ return;
968
+ try {
969
+ await hooks.onRuleMatch(rule2, context);
970
+ } catch {}
971
+ };
972
+ var executeOnRuleSkip = async (hooks, rule2, reason) => {
973
+ if (!hooks.onRuleSkip)
974
+ return;
975
+ try {
976
+ await hooks.onRuleSkip(rule2, reason);
977
+ } catch {}
978
+ };
979
+ var executeOnRuleError = async (hooks, rule2, error) => {
980
+ if (!hooks.onRuleError)
981
+ return;
982
+ try {
983
+ await hooks.onRuleError(rule2, error);
984
+ } catch {}
985
+ };
986
+ var executeOnConsequenceCollected = async (hooks, rule2, consequence) => {
987
+ if (!hooks.onConsequenceCollected)
988
+ return;
989
+ try {
990
+ await hooks.onConsequenceCollected(rule2, consequence);
991
+ } catch {}
992
+ };
993
+ var executeOnCacheHit = async (hooks, key, result) => {
994
+ if (!hooks.onCacheHit)
995
+ return;
996
+ try {
997
+ await hooks.onCacheHit(key, result);
998
+ } catch {}
999
+ };
1000
+ var executeOnCacheMiss = async (hooks, key) => {
1001
+ if (!hooks.onCacheMiss)
1002
+ return;
1003
+ try {
1004
+ await hooks.onCacheMiss(key);
1005
+ } catch {}
1006
+ };
1007
+ var executeOnSlowRule = async (hooks, rule2, timeMs, threshold) => {
1008
+ if (!hooks.onSlowRule)
1009
+ return;
1010
+ try {
1011
+ await hooks.onSlowRule(rule2, timeMs, threshold);
1012
+ } catch {}
1013
+ };
1014
+
1015
+ // src/engine/state.ts
1016
+ var addRule = (state, input) => {
1017
+ const now = new Date;
1018
+ const rule2 = {
1019
+ id: input.id ?? generateId(),
1020
+ name: input.name,
1021
+ description: input.description,
1022
+ conditions: input.conditions,
1023
+ consequences: input.consequences.map((c) => ({
1024
+ type: c.type,
1025
+ payload: c.payload
1026
+ })),
1027
+ priority: input.priority ?? 0,
1028
+ enabled: input.enabled ?? true,
1029
+ stopOnMatch: input.stopOnMatch ?? false,
1030
+ tags: input.tags ?? [],
1031
+ category: input.category,
1032
+ metadata: input.metadata,
1033
+ createdAt: now,
1034
+ updatedAt: now
1035
+ };
1036
+ state.rules.set(rule2.id, rule2);
1037
+ if (!state.ruleOrder.includes(rule2.id)) {
1038
+ state.ruleOrder.push(rule2.id);
1039
+ sortRuleOrder(state);
1040
+ }
1041
+ return rule2;
1042
+ };
1043
+ var addRules = (state, inputs) => {
1044
+ return inputs.map((input) => addRule(state, input));
1045
+ };
1046
+ var removeRule = (state, ruleId) => {
1047
+ const deleted = state.rules.delete(ruleId);
1048
+ if (deleted) {
1049
+ const index = state.ruleOrder.indexOf(ruleId);
1050
+ if (index !== -1) {
1051
+ state.ruleOrder.splice(index, 1);
1052
+ }
1053
+ }
1054
+ return deleted;
1055
+ };
1056
+ var updateRule = (state, ruleId, updates) => {
1057
+ const existing = state.rules.get(ruleId);
1058
+ if (!existing)
1059
+ return;
1060
+ const updated = {
1061
+ ...existing,
1062
+ ...updates.name !== undefined && { name: updates.name },
1063
+ ...updates.description !== undefined && {
1064
+ description: updates.description
1065
+ },
1066
+ ...updates.conditions !== undefined && {
1067
+ conditions: updates.conditions
1068
+ },
1069
+ ...updates.consequences !== undefined && {
1070
+ consequences: updates.consequences.map((c) => ({
1071
+ type: c.type,
1072
+ payload: c.payload
1073
+ }))
1074
+ },
1075
+ ...updates.priority !== undefined && { priority: updates.priority },
1076
+ ...updates.enabled !== undefined && { enabled: updates.enabled },
1077
+ ...updates.stopOnMatch !== undefined && {
1078
+ stopOnMatch: updates.stopOnMatch
1079
+ },
1080
+ ...updates.tags !== undefined && { tags: updates.tags },
1081
+ ...updates.category !== undefined && { category: updates.category },
1082
+ ...updates.metadata !== undefined && { metadata: updates.metadata },
1083
+ updatedAt: new Date
1084
+ };
1085
+ state.rules.set(ruleId, updated);
1086
+ if (updates.priority !== undefined) {
1087
+ sortRuleOrder(state);
1088
+ }
1089
+ return updated;
1090
+ };
1091
+ var getRule = (state, ruleId) => {
1092
+ return state.rules.get(ruleId);
1093
+ };
1094
+ var getRules = (state, filters) => {
1095
+ const rules = [];
1096
+ for (const id of state.ruleOrder) {
1097
+ const rule2 = state.rules.get(id);
1098
+ if (!rule2)
1099
+ continue;
1100
+ if (filters) {
1101
+ if (filters.enabled !== undefined && rule2.enabled !== filters.enabled) {
1102
+ continue;
1103
+ }
1104
+ if (filters.tags && filters.tags.length > 0) {
1105
+ const hasTag = filters.tags.some((tag) => rule2.tags.includes(tag));
1106
+ if (!hasTag)
1107
+ continue;
1108
+ }
1109
+ if (filters.category !== undefined && rule2.category !== filters.category) {
1110
+ continue;
1111
+ }
1112
+ if (filters.ids && filters.ids.length > 0 && !filters.ids.includes(rule2.id)) {
1113
+ continue;
1114
+ }
1115
+ }
1116
+ rules.push(rule2);
1117
+ }
1118
+ return rules;
1119
+ };
1120
+ var enableRule = (state, ruleId) => {
1121
+ const rule2 = state.rules.get(ruleId);
1122
+ if (!rule2)
1123
+ return false;
1124
+ state.rules.set(ruleId, { ...rule2, enabled: true, updatedAt: new Date });
1125
+ return true;
1126
+ };
1127
+ var disableRule = (state, ruleId) => {
1128
+ const rule2 = state.rules.get(ruleId);
1129
+ if (!rule2)
1130
+ return false;
1131
+ state.rules.set(ruleId, { ...rule2, enabled: false, updatedAt: new Date });
1132
+ return true;
1133
+ };
1134
+ var clearRules = (state) => {
1135
+ state.rules.clear();
1136
+ state.ruleOrder.length = 0;
1137
+ };
1138
+ var addRuleSet = (state, input) => {
1139
+ const ruleSet = {
1140
+ id: input.id ?? generateId(),
1141
+ name: input.name,
1142
+ description: input.description,
1143
+ ruleIds: input.ruleIds,
1144
+ enabled: input.enabled ?? true,
1145
+ metadata: input.metadata
1146
+ };
1147
+ state.ruleSets.set(ruleSet.id, ruleSet);
1148
+ return ruleSet;
1149
+ };
1150
+ var getRuleSet = (state, ruleSetId) => {
1151
+ return state.ruleSets.get(ruleSetId);
1152
+ };
1153
+ var getRuleSets = (state) => {
1154
+ return Array.from(state.ruleSets.values());
1155
+ };
1156
+ var removeRuleSet = (state, ruleSetId) => {
1157
+ return state.ruleSets.delete(ruleSetId);
1158
+ };
1159
+ var getRulesInSet = (state, ruleSetId) => {
1160
+ const ruleSet = state.ruleSets.get(ruleSetId);
1161
+ if (!ruleSet || !ruleSet.enabled)
1162
+ return [];
1163
+ const rules = [];
1164
+ for (const ruleId of ruleSet.ruleIds) {
1165
+ const rule2 = state.rules.get(ruleId);
1166
+ if (rule2) {
1167
+ rules.push(rule2);
1168
+ }
1169
+ }
1170
+ return rules;
1171
+ };
1172
+ var sortRuleOrder = (state) => {
1173
+ state.ruleOrder.sort((a, b) => {
1174
+ const ruleA = state.rules.get(a);
1175
+ const ruleB = state.rules.get(b);
1176
+ if (!ruleA || !ruleB)
1177
+ return 0;
1178
+ return ruleB.priority - ruleA.priority;
1179
+ });
1180
+ };
1181
+ var cloneState = (state) => {
1182
+ const newState = createInitialState();
1183
+ for (const [id, rule2] of state.rules) {
1184
+ newState.rules.set(id, { ...rule2 });
1185
+ }
1186
+ for (const [id, ruleSet] of state.ruleSets) {
1187
+ newState.ruleSets.set(id, { ...ruleSet, ruleIds: [...ruleSet.ruleIds] });
1188
+ }
1189
+ newState.ruleOrder.push(...state.ruleOrder);
1190
+ return newState;
1191
+ };
1192
+
1193
+ // src/engine/engine.ts
1194
+ var resolveConfig = (config) => {
1195
+ return {
1196
+ consequences: config.consequences,
1197
+ conflictResolution: config.conflictResolution ?? DEFAULT_ENGINE_CONFIG.conflictResolution,
1198
+ cache: {
1199
+ ...DEFAULT_CACHE_CONFIG,
1200
+ ...config.cache
1201
+ },
1202
+ validation: {
1203
+ ...DEFAULT_VALIDATION_CONFIG,
1204
+ ...config.validation
1205
+ },
1206
+ versioning: {
1207
+ ...DEFAULT_VERSIONING_CONFIG,
1208
+ ...config.versioning
1209
+ },
1210
+ hooks: config.hooks ?? {},
1211
+ logLevel: config.logLevel ?? DEFAULT_ENGINE_CONFIG.logLevel,
1212
+ logger: config.logger ?? console,
1213
+ continueOnError: config.continueOnError ?? DEFAULT_ENGINE_CONFIG.continueOnError,
1214
+ slowRuleThresholdMs: config.slowRuleThresholdMs ?? DEFAULT_ENGINE_CONFIG.slowRuleThresholdMs
1215
+ };
1216
+ };
1217
+ var createEngine = (config = {}) => {
1218
+ const resolvedConfig = resolveConfig(config);
1219
+ const state = createInitialState();
1220
+ const cache = resolvedConfig.cache.enabled ? createCache({
1221
+ ttl: resolvedConfig.cache.ttl,
1222
+ maxSize: resolvedConfig.cache.maxSize
1223
+ }) : createNoopCache();
1224
+ let totalEvaluations = 0;
1225
+ let totalMatches = 0;
1226
+ let totalErrors = 0;
1227
+ let cacheHits = 0;
1228
+ let cacheMisses = 0;
1229
+ let totalEvaluationTime = 0;
1230
+ const evaluate = async (contextData, options = {}) => {
1231
+ const context = {
1232
+ data: contextData,
1233
+ timestamp: new Date,
1234
+ correlationId: generateId()
1235
+ };
1236
+ let rulesToEvaluate = getRules(state, {
1237
+ enabled: options.skipDisabled !== false ? true : undefined,
1238
+ tags: options.tags,
1239
+ category: options.category
1240
+ });
1241
+ if (options.ruleSetId) {
1242
+ const ruleSetRules = getRulesInSet(state, options.ruleSetId);
1243
+ const ruleSetIds = new Set(ruleSetRules.map((r) => r.id));
1244
+ rulesToEvaluate = rulesToEvaluate.filter((r) => ruleSetIds.has(r.id));
1245
+ }
1246
+ rulesToEvaluate = [
1247
+ ...sortRules({
1248
+ field: "priority",
1249
+ direction: "desc"
1250
+ })(rulesToEvaluate)
1251
+ ];
1252
+ if (options.maxRules && options.maxRules > 0) {
1253
+ rulesToEvaluate = rulesToEvaluate.slice(0, options.maxRules);
1254
+ }
1255
+ const cacheKey = !options.bypassCache ? `${hashContext(contextData)}:${hashRules(rulesToEvaluate.map((r) => r.id))}` : null;
1256
+ if (cacheKey && cache.has(cacheKey)) {
1257
+ const cached = cache.get(cacheKey);
1258
+ if (cached) {
1259
+ cacheHits++;
1260
+ await executeOnCacheHit(resolvedConfig.hooks, cacheKey, cached);
1261
+ return { ...cached, cacheHit: true };
1262
+ }
1263
+ }
1264
+ if (cacheKey) {
1265
+ cacheMisses++;
1266
+ await executeOnCacheMiss(resolvedConfig.hooks, cacheKey);
1267
+ }
1268
+ await executeBeforeEvaluation(resolvedConfig.hooks, context, rulesToEvaluate);
1269
+ const { result: evaluationResult, durationMs } = measureTime(() => {
1270
+ const results = [];
1271
+ for (const rule2 of rulesToEvaluate) {
1272
+ const result = evaluateRule(rule2, context, { skipDisabled: true });
1273
+ results.push({ rule: rule2, result });
1274
+ }
1275
+ return results;
1276
+ });
1277
+ const ruleResults = [];
1278
+ const matchedRules = [];
1279
+ const consequences = [];
1280
+ let stoppedEarly = false;
1281
+ let stoppedByRuleId;
1282
+ let rulesErrored = 0;
1283
+ const conflictResolution = options.conflictResolution ?? resolvedConfig.conflictResolution;
1284
+ for (const { rule: rule2, result } of evaluationResult) {
1285
+ await executeBeforeRuleEvaluation(resolvedConfig.hooks, rule2, context);
1286
+ ruleResults.push(result);
1287
+ if (result.error) {
1288
+ rulesErrored++;
1289
+ await executeOnRuleError(resolvedConfig.hooks, rule2, result.error);
1290
+ if (!resolvedConfig.continueOnError) {
1291
+ break;
1292
+ }
1293
+ }
1294
+ if (result.skipped) {
1295
+ await executeOnRuleSkip(resolvedConfig.hooks, rule2, result.skipReason ?? "Unknown");
1296
+ }
1297
+ if (result.evaluationTimeMs > resolvedConfig.slowRuleThresholdMs) {
1298
+ await executeOnSlowRule(resolvedConfig.hooks, rule2, result.evaluationTimeMs, resolvedConfig.slowRuleThresholdMs);
1299
+ }
1300
+ await executeAfterRuleEvaluation(resolvedConfig.hooks, rule2, result);
1301
+ if (result.matched) {
1302
+ matchedRules.push(rule2);
1303
+ for (const consequence of result.consequences) {
1304
+ consequences.push(consequence);
1305
+ await executeOnConsequenceCollected(resolvedConfig.hooks, rule2, consequence);
1306
+ }
1307
+ await executeOnRuleMatch(resolvedConfig.hooks, rule2, context);
1308
+ if (rule2.stopOnMatch) {
1309
+ stoppedEarly = true;
1310
+ stoppedByRuleId = rule2.id;
1311
+ break;
1312
+ }
1313
+ if (conflictResolution === "first-match") {
1314
+ stoppedEarly = true;
1315
+ stoppedByRuleId = rule2.id;
1316
+ break;
1317
+ }
1318
+ }
1319
+ }
1320
+ totalEvaluations++;
1321
+ totalMatches += matchedRules.length;
1322
+ totalErrors += rulesErrored;
1323
+ totalEvaluationTime += durationMs;
1324
+ const executionResult = {
1325
+ context,
1326
+ results: ruleResults,
1327
+ matchedRules,
1328
+ consequences,
1329
+ totalRulesEvaluated: ruleResults.length,
1330
+ totalRulesMatched: matchedRules.length,
1331
+ totalRulesSkipped: ruleResults.filter((r) => r.skipped).length,
1332
+ totalRulesErrored: rulesErrored,
1333
+ executionTimeMs: durationMs,
1334
+ stoppedEarly,
1335
+ stoppedByRuleId,
1336
+ cacheHit: false
1337
+ };
1338
+ if (cacheKey) {
1339
+ cache.set(cacheKey, executionResult);
1340
+ }
1341
+ await executeAfterEvaluation(resolvedConfig.hooks, executionResult);
1342
+ return executionResult;
1343
+ };
1344
+ return {
1345
+ addRule: (input) => addRule(state, input),
1346
+ addRules: (inputs) => addRules(state, inputs),
1347
+ removeRule: (ruleId) => {
1348
+ const result = removeRule(state, ruleId);
1349
+ if (result)
1350
+ cache.clear();
1351
+ return result;
1352
+ },
1353
+ updateRule: (ruleId, updates) => {
1354
+ const result = updateRule(state, ruleId, updates);
1355
+ if (result)
1356
+ cache.clear();
1357
+ return result;
1358
+ },
1359
+ getRule: (ruleId) => getRule(state, ruleId),
1360
+ getRules: (filters) => getRules(state, filters),
1361
+ enableRule: (ruleId) => {
1362
+ const result = enableRule(state, ruleId);
1363
+ if (result)
1364
+ cache.clear();
1365
+ return result;
1366
+ },
1367
+ disableRule: (ruleId) => {
1368
+ const result = disableRule(state, ruleId);
1369
+ if (result)
1370
+ cache.clear();
1371
+ return result;
1372
+ },
1373
+ clearRules: () => {
1374
+ clearRules(state);
1375
+ cache.clear();
1376
+ },
1377
+ addRuleSet: (input) => addRuleSet(state, input),
1378
+ getRuleSet: (ruleSetId) => getRuleSet(state, ruleSetId),
1379
+ getRuleSets: () => getRuleSets(state),
1380
+ removeRuleSet: (ruleSetId) => removeRuleSet(state, ruleSetId),
1381
+ evaluate,
1382
+ clearCache: () => cache.clear(),
1383
+ getCacheStats: () => cache.getStats(),
1384
+ getState: () => ({
1385
+ rules: state.rules,
1386
+ ruleSets: state.ruleSets,
1387
+ ruleOrder: state.ruleOrder
1388
+ }),
1389
+ getStats: () => {
1390
+ const enabledRules = Array.from(state.rules.values()).filter((r) => r.enabled).length;
1391
+ const cacheStats = cache.getStats();
1392
+ return {
1393
+ totalRules: state.rules.size,
1394
+ enabledRules,
1395
+ disabledRules: state.rules.size - enabledRules,
1396
+ totalRuleSets: state.ruleSets.size,
1397
+ totalEvaluations,
1398
+ totalMatches,
1399
+ totalErrors,
1400
+ avgEvaluationTimeMs: totalEvaluations > 0 ? totalEvaluationTime / totalEvaluations : 0,
1401
+ cacheHits,
1402
+ cacheMisses,
1403
+ cacheHitRate: cacheHits + cacheMisses > 0 ? cacheHits / (cacheHits + cacheMisses) : 0
1404
+ };
1405
+ }
1406
+ };
1407
+ };
1408
+ // src/optimizer/index-builder.ts
1409
+ import {
1410
+ isConditionGroup as isConditionGroup2
1411
+ } from "@f-o-t/condition-evaluator";
1412
+ var DEFAULT_OPTIONS = {
1413
+ indexByField: true,
1414
+ indexByTag: true,
1415
+ indexByCategory: true,
1416
+ indexByPriority: true
1417
+ };
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
+ var buildIndex = (rules, options = {}) => {
1433
+ const opts = { ...DEFAULT_OPTIONS, ...options };
1434
+ const byId = new Map;
1435
+ const byField = new Map;
1436
+ const byTag = new Map;
1437
+ const byCategory = new Map;
1438
+ const byPriority = new Map;
1439
+ for (const rule2 of rules) {
1440
+ byId.set(rule2.id, rule2);
1441
+ if (opts.indexByField) {
1442
+ const fields = collectFields2(rule2.conditions);
1443
+ for (const field of fields) {
1444
+ const existing = byField.get(field) ?? [];
1445
+ existing.push(rule2);
1446
+ byField.set(field, existing);
1447
+ }
1448
+ }
1449
+ if (opts.indexByTag) {
1450
+ for (const tag of rule2.tags) {
1451
+ const existing = byTag.get(tag) ?? [];
1452
+ existing.push(rule2);
1453
+ byTag.set(tag, existing);
1454
+ }
1455
+ }
1456
+ if (opts.indexByCategory && rule2.category) {
1457
+ const existing = byCategory.get(rule2.category) ?? [];
1458
+ existing.push(rule2);
1459
+ byCategory.set(rule2.category, existing);
1460
+ }
1461
+ if (opts.indexByPriority) {
1462
+ const existing = byPriority.get(rule2.priority) ?? [];
1463
+ existing.push(rule2);
1464
+ byPriority.set(rule2.priority, existing);
1465
+ }
1466
+ }
1467
+ const sortedByPriority = [...rules].sort((a, b) => b.priority - a.priority);
1468
+ return {
1469
+ byField,
1470
+ byTag,
1471
+ byCategory,
1472
+ byPriority,
1473
+ byId,
1474
+ sortedByPriority
1475
+ };
1476
+ };
1477
+ var getRulesByField = (index, field) => {
1478
+ return index.byField.get(field) ?? [];
1479
+ };
1480
+ var getRulesByFields = (index, fields, mode = "any") => {
1481
+ if (fields.length === 0)
1482
+ return [];
1483
+ const ruleSets = fields.map((f) => new Set(getRulesByField(index, f)));
1484
+ if (mode === "any") {
1485
+ const combined = new Set;
1486
+ for (const ruleSet of ruleSets) {
1487
+ for (const rule2 of ruleSet) {
1488
+ combined.add(rule2);
1489
+ }
1490
+ }
1491
+ return [...combined];
1492
+ }
1493
+ const [first, ...rest] = ruleSets;
1494
+ if (!first)
1495
+ return [];
1496
+ return [...first].filter((rule2) => rest.every((set) => set.has(rule2)));
1497
+ };
1498
+ var getRulesByTag = (index, tag) => {
1499
+ return index.byTag.get(tag) ?? [];
1500
+ };
1501
+ var getRulesByTags = (index, tags, mode = "any") => {
1502
+ if (tags.length === 0)
1503
+ return [];
1504
+ const ruleSets = tags.map((t) => new Set(getRulesByTag(index, t)));
1505
+ if (mode === "any") {
1506
+ const combined = new Set;
1507
+ for (const ruleSet of ruleSets) {
1508
+ for (const rule2 of ruleSet) {
1509
+ combined.add(rule2);
1510
+ }
1511
+ }
1512
+ return [...combined];
1513
+ }
1514
+ const [first, ...rest] = ruleSets;
1515
+ if (!first)
1516
+ return [];
1517
+ return [...first].filter((rule2) => rest.every((set) => set.has(rule2)));
1518
+ };
1519
+ var getRulesByCategory = (index, category) => {
1520
+ return index.byCategory.get(category) ?? [];
1521
+ };
1522
+ var getRulesByPriority = (index, priority) => {
1523
+ return index.byPriority.get(priority) ?? [];
1524
+ };
1525
+ var getRulesByPriorityRange = (index, minPriority, maxPriority) => {
1526
+ return index.sortedByPriority.filter((rule2) => rule2.priority >= minPriority && rule2.priority <= maxPriority);
1527
+ };
1528
+ var getRuleById = (index, id) => {
1529
+ return index.byId.get(id);
1530
+ };
1531
+ var filterRulesForContext = (index, contextFields) => {
1532
+ const relevantRules = new Set;
1533
+ for (const field of contextFields) {
1534
+ const rules = index.byField.get(field);
1535
+ if (rules) {
1536
+ for (const rule2 of rules) {
1537
+ relevantRules.add(rule2);
1538
+ }
1539
+ }
1540
+ }
1541
+ return [...relevantRules].sort((a, b) => b.priority - a.priority);
1542
+ };
1543
+ var analyzeOptimizations = (rules) => {
1544
+ const suggestions = [];
1545
+ const fieldUsage = new Map;
1546
+ for (const rule2 of rules) {
1547
+ const fields = collectFields2(rule2.conditions);
1548
+ for (const field of fields) {
1549
+ fieldUsage.set(field, (fieldUsage.get(field) ?? 0) + 1);
1550
+ }
1551
+ }
1552
+ const frequentFields = [...fieldUsage.entries()].filter(([, count]) => count > rules.length * 0.5).map(([field]) => field);
1553
+ if (frequentFields.length > 0) {
1554
+ suggestions.push({
1555
+ type: "INDEX",
1556
+ severity: "medium",
1557
+ message: `Consider creating indexes for frequently used fields: ${frequentFields.join(", ")}`,
1558
+ details: {
1559
+ fields: frequentFields,
1560
+ usagePercentage: frequentFields.map((f) => ({
1561
+ field: f,
1562
+ percentage: (fieldUsage.get(f) ?? 0) / rules.length * 100
1563
+ }))
1564
+ }
1565
+ });
1566
+ }
1567
+ const priorityGroups = new Map;
1568
+ for (const rule2 of rules) {
1569
+ const existing = priorityGroups.get(rule2.priority) ?? [];
1570
+ existing.push(rule2);
1571
+ priorityGroups.set(rule2.priority, existing);
1572
+ }
1573
+ const largePriorityGroups = [...priorityGroups.entries()].filter(([, group]) => group.length > 5);
1574
+ if (largePriorityGroups.length > 0) {
1575
+ for (const [priority, group] of largePriorityGroups) {
1576
+ suggestions.push({
1577
+ type: "REORDER",
1578
+ severity: "low",
1579
+ message: `${group.length} rules share priority ${priority}. Consider differentiating priorities for more predictable execution order.`,
1580
+ ruleIds: group.map((r) => r.id),
1581
+ details: { priority, count: group.length }
1582
+ });
1583
+ }
1584
+ }
1585
+ const disabledRules = rules.filter((r) => !r.enabled);
1586
+ if (disabledRules.length > rules.length * 0.3) {
1587
+ suggestions.push({
1588
+ type: "SIMPLIFY",
1589
+ severity: "low",
1590
+ message: `${disabledRules.length} rules (${(disabledRules.length / rules.length * 100).toFixed(1)}%) are disabled. Consider removing unused rules.`,
1591
+ ruleIds: disabledRules.map((r) => r.id),
1592
+ details: {
1593
+ disabledCount: disabledRules.length,
1594
+ totalCount: rules.length
1595
+ }
1596
+ });
1597
+ }
1598
+ if (rules.length > 100) {
1599
+ suggestions.push({
1600
+ type: "CACHE",
1601
+ severity: "high",
1602
+ message: `Rule set contains ${rules.length} rules. Enable caching for better performance.`,
1603
+ details: { ruleCount: rules.length }
1604
+ });
1605
+ }
1606
+ return suggestions;
1607
+ };
1608
+ var getIndexStats = (index) => {
1609
+ const totalRules = index.byId.size;
1610
+ const fieldRuleCounts = [...index.byField.values()].map((r) => r.length);
1611
+ const tagRuleCounts = [...index.byTag.values()].map((r) => r.length);
1612
+ return {
1613
+ totalRules,
1614
+ uniqueFields: index.byField.size,
1615
+ uniqueTags: index.byTag.size,
1616
+ uniqueCategories: index.byCategory.size,
1617
+ uniquePriorities: index.byPriority.size,
1618
+ averageRulesPerField: fieldRuleCounts.length > 0 ? fieldRuleCounts.reduce((a, b) => a + b, 0) / fieldRuleCounts.length : 0,
1619
+ averageRulesPerTag: tagRuleCounts.length > 0 ? tagRuleCounts.reduce((a, b) => a + b, 0) / tagRuleCounts.length : 0
1620
+ };
1621
+ };
1622
+ // src/serialization/serializer.ts
1623
+ var EXPORT_VERSION = "1.0.0";
1624
+ var serializeRule = (rule2) => ({
1625
+ id: rule2.id,
1626
+ name: rule2.name,
1627
+ description: rule2.description,
1628
+ conditions: rule2.conditions,
1629
+ consequences: rule2.consequences.map((c) => ({
1630
+ type: c.type,
1631
+ payload: c.payload
1632
+ })),
1633
+ priority: rule2.priority,
1634
+ enabled: rule2.enabled,
1635
+ stopOnMatch: rule2.stopOnMatch,
1636
+ tags: rule2.tags,
1637
+ category: rule2.category,
1638
+ metadata: rule2.metadata,
1639
+ createdAt: rule2.createdAt.toISOString(),
1640
+ updatedAt: rule2.updatedAt.toISOString()
1641
+ });
1642
+ var deserializeRule = (serialized, options = {}) => {
1643
+ const id = options.generateNewIds ? `${options.idPrefix ?? ""}${generateId()}` : serialized.id;
1644
+ const now = new Date;
1645
+ return {
1646
+ id,
1647
+ name: serialized.name,
1648
+ description: serialized.description,
1649
+ conditions: serialized.conditions,
1650
+ consequences: serialized.consequences,
1651
+ priority: serialized.priority,
1652
+ enabled: serialized.enabled,
1653
+ stopOnMatch: serialized.stopOnMatch,
1654
+ tags: serialized.tags,
1655
+ category: serialized.category,
1656
+ metadata: serialized.metadata,
1657
+ createdAt: options.preserveDates ? new Date(serialized.createdAt) : now,
1658
+ updatedAt: options.preserveDates ? new Date(serialized.updatedAt) : now
1659
+ };
1660
+ };
1661
+ var serializeRuleSet = (ruleSet) => ({
1662
+ id: ruleSet.id,
1663
+ name: ruleSet.name,
1664
+ description: ruleSet.description,
1665
+ ruleIds: ruleSet.ruleIds,
1666
+ enabled: ruleSet.enabled,
1667
+ metadata: ruleSet.metadata
1668
+ });
1669
+ var deserializeRuleSet = (serialized, idMapping, options = {}) => {
1670
+ const id = options.generateNewIds ? `${options.idPrefix ?? ""}${generateId()}` : serialized.id;
1671
+ const ruleIds = serialized.ruleIds.map((oldId) => idMapping.get(oldId) ?? oldId);
1672
+ return {
1673
+ id,
1674
+ name: serialized.name,
1675
+ description: serialized.description,
1676
+ ruleIds,
1677
+ enabled: serialized.enabled,
1678
+ metadata: serialized.metadata
1679
+ };
1680
+ };
1681
+ var exportRules = (rules, ruleSets, metadata) => ({
1682
+ version: EXPORT_VERSION,
1683
+ exportedAt: new Date().toISOString(),
1684
+ rules: rules.map(serializeRule),
1685
+ ruleSets: ruleSets?.map(serializeRuleSet),
1686
+ metadata
1687
+ });
1688
+ var exportToJson = (rules, ruleSets, metadata) => {
1689
+ const exportData = exportRules(rules, ruleSets, metadata);
1690
+ return JSON.stringify(exportData, null, 2);
1691
+ };
1692
+ var importRules = (data, options = {}) => {
1693
+ const rules = [];
1694
+ const ruleSets = [];
1695
+ const errors = [];
1696
+ const idMapping = new Map;
1697
+ for (let i = 0;i < data.rules.length; i++) {
1698
+ try {
1699
+ const serialized = data.rules[i];
1700
+ const rule2 = deserializeRule(serialized, options);
1701
+ idMapping.set(serialized.id, rule2.id);
1702
+ rules.push(rule2);
1703
+ } catch (error) {
1704
+ errors.push({
1705
+ index: i,
1706
+ type: "rule",
1707
+ message: error instanceof Error ? error.message : String(error)
1708
+ });
1709
+ }
1710
+ }
1711
+ if (data.ruleSets) {
1712
+ for (let i = 0;i < data.ruleSets.length; i++) {
1713
+ try {
1714
+ const serialized = data.ruleSets[i];
1715
+ const ruleSet = deserializeRuleSet(serialized, idMapping, options);
1716
+ ruleSets.push(ruleSet);
1717
+ } catch (error) {
1718
+ errors.push({
1719
+ index: i,
1720
+ type: "ruleSet",
1721
+ message: error instanceof Error ? error.message : String(error)
1722
+ });
1723
+ }
1724
+ }
1725
+ }
1726
+ return {
1727
+ success: errors.length === 0,
1728
+ rules,
1729
+ ruleSets,
1730
+ errors,
1731
+ idMapping
1732
+ };
1733
+ };
1734
+ var importFromJson = (json, options = {}) => {
1735
+ try {
1736
+ const data = JSON.parse(json);
1737
+ return importRules(data, options);
1738
+ } catch (error) {
1739
+ return {
1740
+ success: false,
1741
+ rules: [],
1742
+ ruleSets: [],
1743
+ errors: [
1744
+ {
1745
+ index: -1,
1746
+ type: "rule",
1747
+ message: `Invalid JSON: ${error instanceof Error ? error.message : String(error)}`
1748
+ }
1749
+ ],
1750
+ idMapping: new Map
1751
+ };
1752
+ }
1753
+ };
1754
+ var cloneRule = (rule2, newId, newName) => {
1755
+ const now = new Date;
1756
+ return {
1757
+ ...rule2,
1758
+ id: newId ?? generateId(),
1759
+ name: newName ?? `${rule2.name} (Copy)`,
1760
+ createdAt: now,
1761
+ updatedAt: now
1762
+ };
1763
+ };
1764
+ var mergeRuleSets = (baseRules, incomingRules, strategy = "replace") => {
1765
+ const ruleMap = new Map;
1766
+ for (const rule2 of baseRules) {
1767
+ ruleMap.set(rule2.id, rule2);
1768
+ }
1769
+ for (const rule2 of incomingRules) {
1770
+ const existing = ruleMap.get(rule2.id);
1771
+ if (!existing) {
1772
+ ruleMap.set(rule2.id, rule2);
1773
+ } else {
1774
+ switch (strategy) {
1775
+ case "replace":
1776
+ ruleMap.set(rule2.id, rule2);
1777
+ break;
1778
+ case "skip":
1779
+ break;
1780
+ case "merge": {
1781
+ const merged = {
1782
+ ...existing,
1783
+ ...rule2,
1784
+ tags: [...new Set([...existing.tags, ...rule2.tags])],
1785
+ metadata: { ...existing.metadata, ...rule2.metadata },
1786
+ updatedAt: new Date
1787
+ };
1788
+ ruleMap.set(rule2.id, merged);
1789
+ break;
1790
+ }
1791
+ }
1792
+ }
1793
+ }
1794
+ return [...ruleMap.values()];
1795
+ };
1796
+ var diffRuleSets = (oldRules, newRules) => {
1797
+ const oldMap = new Map(oldRules.map((r) => [r.id, r]));
1798
+ const newMap = new Map(newRules.map((r) => [r.id, r]));
1799
+ const added = [];
1800
+ const removed = [];
1801
+ const modified = [];
1802
+ const unchanged = [];
1803
+ for (const [id, newRule] of newMap) {
1804
+ const oldRule = oldMap.get(id);
1805
+ if (!oldRule) {
1806
+ added.push(newRule);
1807
+ } else if (JSON.stringify({ ...oldRule, updatedAt: null, createdAt: null }) !== JSON.stringify({ ...newRule, updatedAt: null, createdAt: null })) {
1808
+ modified.push({ old: oldRule, new: newRule });
1809
+ } else {
1810
+ unchanged.push(newRule);
1811
+ }
1812
+ }
1813
+ for (const [id, oldRule] of oldMap) {
1814
+ if (!newMap.has(id)) {
1815
+ removed.push(oldRule);
1816
+ }
1817
+ }
1818
+ return { added, removed, modified, unchanged };
1819
+ };
1820
+ // src/simulation/simulator.ts
1821
+ var toEvaluationContext = (simContext) => ({
1822
+ data: simContext.data,
1823
+ timestamp: new Date,
1824
+ metadata: simContext.metadata
1825
+ });
1826
+ var simulate = (rules, context) => {
1827
+ const startTime = performance.now();
1828
+ const enabledRules = rules.filter((r) => r.enabled);
1829
+ const evalContext = toEvaluationContext(context);
1830
+ const results = evaluateRules(enabledRules, evalContext);
1831
+ const matchedRules = results.results.filter((r) => r.matched);
1832
+ const unmatchedRules = [];
1833
+ for (const result of results.results) {
1834
+ if (!result.matched) {
1835
+ const reason = result.skipped ? result.skipReason ?? "Skipped" : result.error ? `Error: ${result.error.message}` : "Conditions not met";
1836
+ unmatchedRules.push({
1837
+ ruleId: result.ruleId,
1838
+ ruleName: result.ruleName,
1839
+ reason
1840
+ });
1841
+ }
1842
+ }
1843
+ const consequences = [];
1844
+ for (const match of matchedRules) {
1845
+ for (const consequence of match.consequences) {
1846
+ consequences.push({
1847
+ type: consequence.type,
1848
+ payload: consequence.payload,
1849
+ ruleId: consequence.ruleId,
1850
+ ruleName: consequence.ruleName ?? match.ruleName
1851
+ });
1852
+ }
1853
+ }
1854
+ const executionTimeMs = performance.now() - startTime;
1855
+ return {
1856
+ context,
1857
+ matchedRules,
1858
+ unmatchedRules,
1859
+ consequences,
1860
+ executionTimeMs
1861
+ };
1862
+ };
1863
+ var simulateSingleRule = (rule2, context) => {
1864
+ const evalContext = toEvaluationContext(context);
1865
+ const result = evaluateRule(rule2, evalContext);
1866
+ return {
1867
+ matched: result.matched,
1868
+ conditionResult: result.conditionResult,
1869
+ consequences: result.matched ? rule2.consequences.map((c) => ({ type: c.type, payload: c.payload })) : []
1870
+ };
1871
+ };
1872
+ var whatIf = (originalRules, modifiedRules, context) => {
1873
+ const original = simulate(originalRules, context);
1874
+ const modified = simulate(modifiedRules, context);
1875
+ const originalMatchIds = new Set(original.matchedRules.map((r) => r.ruleId));
1876
+ const modifiedMatchIds = new Set(modified.matchedRules.map((r) => r.ruleId));
1877
+ const newMatches = [...modifiedMatchIds].filter((id) => !originalMatchIds.has(id));
1878
+ const lostMatches = [...originalMatchIds].filter((id) => !modifiedMatchIds.has(id));
1879
+ const consequenceChanges = [];
1880
+ const originalConsequenceKeys = new Set(original.consequences.map((c) => `${c.ruleId}:${c.type}`));
1881
+ const modifiedConsequenceKeys = new Set(modified.consequences.map((c) => `${c.ruleId}:${c.type}`));
1882
+ for (const key of modifiedConsequenceKeys) {
1883
+ if (!originalConsequenceKeys.has(key)) {
1884
+ const [ruleId, consequenceType] = key.split(":");
1885
+ consequenceChanges.push({
1886
+ type: "added",
1887
+ consequenceType,
1888
+ ruleId
1889
+ });
1890
+ }
1891
+ }
1892
+ for (const key of originalConsequenceKeys) {
1893
+ if (!modifiedConsequenceKeys.has(key)) {
1894
+ const [ruleId, consequenceType] = key.split(":");
1895
+ consequenceChanges.push({
1896
+ type: "removed",
1897
+ consequenceType,
1898
+ ruleId
1899
+ });
1900
+ }
1901
+ }
1902
+ return {
1903
+ original,
1904
+ modified,
1905
+ differences: {
1906
+ newMatches,
1907
+ lostMatches,
1908
+ consequenceChanges
1909
+ }
1910
+ };
1911
+ };
1912
+ var batchSimulate = (rules, contexts) => {
1913
+ const results = contexts.map((context) => simulate(rules, context));
1914
+ const ruleMatchFrequency = new Map;
1915
+ const consequenceFrequency = new Map;
1916
+ let totalMatchedRules = 0;
1917
+ let totalExecutionTime = 0;
1918
+ for (const result of results) {
1919
+ totalMatchedRules += result.matchedRules.length;
1920
+ totalExecutionTime += result.executionTimeMs;
1921
+ for (const match of result.matchedRules) {
1922
+ const count = ruleMatchFrequency.get(match.ruleId) ?? 0;
1923
+ ruleMatchFrequency.set(match.ruleId, count + 1);
1924
+ }
1925
+ for (const consequence of result.consequences) {
1926
+ const key = consequence.type;
1927
+ const count = consequenceFrequency.get(key) ?? 0;
1928
+ consequenceFrequency.set(key, count + 1);
1929
+ }
1930
+ }
1931
+ return {
1932
+ results,
1933
+ summary: {
1934
+ totalContexts: contexts.length,
1935
+ averageMatchedRules: contexts.length > 0 ? totalMatchedRules / contexts.length : 0,
1936
+ ruleMatchFrequency,
1937
+ consequenceFrequency,
1938
+ averageExecutionTimeMs: contexts.length > 0 ? totalExecutionTime / contexts.length : 0
1939
+ }
1940
+ };
1941
+ };
1942
+ var findRulesAffectedByContextChange = (rules, originalContext, modifiedContext) => {
1943
+ const originalResult = simulate(rules, originalContext);
1944
+ const modifiedResult = simulate(rules, modifiedContext);
1945
+ const originalMatchIds = new Set(originalResult.matchedRules.map((r) => r.ruleId));
1946
+ const modifiedMatchIds = new Set(modifiedResult.matchedRules.map((r) => r.ruleId));
1947
+ const becameTrue = [];
1948
+ const becameFalse = [];
1949
+ const unchanged = [];
1950
+ for (const rule2 of rules) {
1951
+ const wasMatched = originalMatchIds.has(rule2.id);
1952
+ const isMatched = modifiedMatchIds.has(rule2.id);
1953
+ if (!wasMatched && isMatched) {
1954
+ becameTrue.push(rule2);
1955
+ } else if (wasMatched && !isMatched) {
1956
+ becameFalse.push(rule2);
1957
+ } else {
1958
+ unchanged.push(rule2);
1959
+ }
1960
+ }
1961
+ return { becameTrue, becameFalse, unchanged };
1962
+ };
1963
+ var formatSimulationResult = (result) => {
1964
+ const lines = [
1965
+ "=== Simulation Result ===",
1966
+ "",
1967
+ `Execution Time: ${result.executionTimeMs.toFixed(2)}ms`,
1968
+ "",
1969
+ `Matched Rules (${result.matchedRules.length}):`
1970
+ ];
1971
+ for (const match of result.matchedRules) {
1972
+ lines.push(` - ${match.ruleName} (${match.ruleId})`);
1973
+ }
1974
+ lines.push("");
1975
+ lines.push(`Unmatched Rules (${result.unmatchedRules.length}):`);
1976
+ for (const unmatched of result.unmatchedRules) {
1977
+ lines.push(` - ${unmatched.ruleName}: ${unmatched.reason}`);
1978
+ }
1979
+ lines.push("");
1980
+ lines.push(`Consequences (${result.consequences.length}):`);
1981
+ for (const consequence of result.consequences) {
1982
+ lines.push(` - ${consequence.type} from "${consequence.ruleName}"`);
1983
+ }
1984
+ return lines.join(`
1985
+ `);
1986
+ };
1987
+ // 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) => {
1994
+ return typeof val === "object" && val !== null && "id" in val && "operator" in val;
1995
+ }, "Invalid condition group"),
1996
+ consequences: z.array(z.object({
1997
+ type: z.string(),
1998
+ payload: z.unknown()
1999
+ })),
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)
2008
+ });
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()
2016
+ });
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
+ // src/validation/conflicts.ts
2031
+ import {
2032
+ isConditionGroup as isConditionGroup3
2033
+ } from "@f-o-t/condition-evaluator";
2034
+ var DEFAULT_OPTIONS2 = {
2035
+ checkDuplicateIds: true,
2036
+ checkDuplicateConditions: true,
2037
+ checkOverlappingConditions: true,
2038
+ checkPriorityCollisions: true,
2039
+ checkUnreachableRules: true
2040
+ };
2041
+ var collectConditionFields = (condition) => {
2042
+ const fields = new Set;
2043
+ if (isConditionGroup3(condition)) {
2044
+ for (const child of condition.conditions) {
2045
+ const childFields = collectConditionFields(child);
2046
+ for (const field of childFields) {
2047
+ fields.add(field);
2048
+ }
2049
+ }
2050
+ } else {
2051
+ fields.add(condition.field);
2052
+ }
2053
+ return fields;
2054
+ };
2055
+ var hashConditionGroup = (condition) => {
2056
+ const serialize = (c) => {
2057
+ if (isConditionGroup3(c)) {
2058
+ const sortedConditions = [...c.conditions].map((child) => serialize(child)).sort();
2059
+ return `GROUP:${c.operator}:[${sortedConditions.join(",")}]`;
2060
+ }
2061
+ return `COND:${c.type}:${c.field}:${c.operator}:${JSON.stringify(c.value)}`;
2062
+ };
2063
+ return serialize(condition);
2064
+ };
2065
+ var getConditionOperatorValues = (condition, field) => {
2066
+ const results = [];
2067
+ const traverse = (c) => {
2068
+ if (isConditionGroup3(c)) {
2069
+ for (const child of c.conditions) {
2070
+ traverse(child);
2071
+ }
2072
+ } else if (c.field === field) {
2073
+ results.push({ operator: c.operator, value: c.value });
2074
+ }
2075
+ };
2076
+ traverse(condition);
2077
+ return results;
2078
+ };
2079
+ var areConditionsOverlapping = (conditions1, conditions2) => {
2080
+ const fields1 = collectConditionFields(conditions1);
2081
+ const fields2 = collectConditionFields(conditions2);
2082
+ const commonFields = new Set([...fields1].filter((f) => fields2.has(f)));
2083
+ if (commonFields.size === 0)
2084
+ return false;
2085
+ for (const field of commonFields) {
2086
+ const ops1 = getConditionOperatorValues(conditions1, field);
2087
+ const ops2 = getConditionOperatorValues(conditions2, field);
2088
+ for (const op1 of ops1) {
2089
+ for (const op2 of ops2) {
2090
+ if (op1.operator === op2.operator && op1.value === op2.value) {
2091
+ return true;
2092
+ }
2093
+ if (op1.operator === "gt" && op2.operator === "lt" || op1.operator === "lt" && op2.operator === "gt" || op1.operator === "gte" && op2.operator === "lte" || op1.operator === "lte" && op2.operator === "gte") {
2094
+ const val1 = op1.value;
2095
+ const val2 = op2.value;
2096
+ if (typeof val1 === "number" && typeof val2 === "number") {
2097
+ if (op1.operator === "gt" && op2.operator === "lt") {
2098
+ if (val1 < val2)
2099
+ return true;
2100
+ }
2101
+ if (op1.operator === "lt" && op2.operator === "gt") {
2102
+ if (val1 > val2)
2103
+ return true;
2104
+ }
2105
+ }
2106
+ }
2107
+ }
2108
+ }
2109
+ }
2110
+ return false;
2111
+ };
2112
+ var findDuplicateIds = (rules) => {
2113
+ const conflicts = [];
2114
+ const idMap = new Map;
2115
+ for (const rule2 of rules) {
2116
+ const existing = idMap.get(rule2.id) ?? [];
2117
+ existing.push(rule2);
2118
+ idMap.set(rule2.id, existing);
2119
+ }
2120
+ for (const [id, duplicates] of idMap) {
2121
+ if (duplicates.length > 1) {
2122
+ conflicts.push({
2123
+ type: "DUPLICATE_ID",
2124
+ severity: "error",
2125
+ message: `Multiple rules share the same ID: ${id}`,
2126
+ ruleIds: duplicates.map((r) => r.id),
2127
+ rules: duplicates,
2128
+ details: { duplicateId: id, count: duplicates.length }
2129
+ });
2130
+ }
2131
+ }
2132
+ return conflicts;
2133
+ };
2134
+ var findDuplicateConditions = (rules) => {
2135
+ const conflicts = [];
2136
+ const hashMap = new Map;
2137
+ for (const rule2 of rules) {
2138
+ const hash = hashConditionGroup(rule2.conditions);
2139
+ const existing = hashMap.get(hash) ?? [];
2140
+ existing.push(rule2);
2141
+ hashMap.set(hash, existing);
2142
+ }
2143
+ for (const [hash, duplicates] of hashMap) {
2144
+ if (duplicates.length > 1) {
2145
+ conflicts.push({
2146
+ type: "DUPLICATE_CONDITIONS",
2147
+ severity: "warning",
2148
+ message: `Multiple rules have identical conditions: ${duplicates.map((r) => r.name).join(", ")}`,
2149
+ ruleIds: duplicates.map((r) => r.id),
2150
+ rules: duplicates,
2151
+ details: { conditionHash: hash, count: duplicates.length }
2152
+ });
2153
+ }
2154
+ }
2155
+ return conflicts;
2156
+ };
2157
+ var findOverlappingConditions = (rules) => {
2158
+ const conflicts = [];
2159
+ const checked = new Set;
2160
+ for (let i = 0;i < rules.length; i++) {
2161
+ for (let j = i + 1;j < rules.length; j++) {
2162
+ const rule1 = rules[i];
2163
+ const rule2 = rules[j];
2164
+ const key = [rule1.id, rule2.id].sort().join(":");
2165
+ if (checked.has(key))
2166
+ continue;
2167
+ checked.add(key);
2168
+ if (areConditionsOverlapping(rule1.conditions, rule2.conditions)) {
2169
+ conflicts.push({
2170
+ type: "OVERLAPPING_CONDITIONS",
2171
+ severity: "info",
2172
+ message: `Rules "${rule1.name}" and "${rule2.name}" have overlapping conditions`,
2173
+ ruleIds: [rule1.id, rule2.id],
2174
+ rules: [rule1, rule2],
2175
+ details: {
2176
+ rule1Fields: [...collectConditionFields(rule1.conditions)],
2177
+ rule2Fields: [...collectConditionFields(rule2.conditions)]
2178
+ }
2179
+ });
2180
+ }
2181
+ }
2182
+ }
2183
+ return conflicts;
2184
+ };
2185
+ var findPriorityCollisions = (rules) => {
2186
+ const conflicts = [];
2187
+ const priorityMap = new Map;
2188
+ for (const rule2 of rules) {
2189
+ const existing = priorityMap.get(rule2.priority) ?? [];
2190
+ existing.push(rule2);
2191
+ priorityMap.set(rule2.priority, existing);
2192
+ }
2193
+ for (const [priority, rulesWithPriority] of priorityMap) {
2194
+ if (rulesWithPriority.length > 1) {
2195
+ const overlappingPairs = new Set;
2196
+ for (let i = 0;i < rulesWithPriority.length; i++) {
2197
+ for (let j = i + 1;j < rulesWithPriority.length; j++) {
2198
+ const r1 = rulesWithPriority[i];
2199
+ const r2 = rulesWithPriority[j];
2200
+ if (areConditionsOverlapping(r1.conditions, r2.conditions)) {
2201
+ overlappingPairs.add(r1.id);
2202
+ overlappingPairs.add(r2.id);
2203
+ }
2204
+ }
2205
+ }
2206
+ if (overlappingPairs.size > 1) {
2207
+ const overlapping = rulesWithPriority.filter((r) => overlappingPairs.has(r.id));
2208
+ conflicts.push({
2209
+ type: "PRIORITY_COLLISION",
2210
+ severity: "warning",
2211
+ message: `Multiple overlapping rules share priority ${priority}: ${overlapping.map((r) => r.name).join(", ")}`,
2212
+ ruleIds: overlapping.map((r) => r.id),
2213
+ rules: overlapping,
2214
+ details: { priority, count: overlapping.length }
2215
+ });
2216
+ }
2217
+ }
2218
+ }
2219
+ return conflicts;
2220
+ };
2221
+ var findUnreachableRules = (rules) => {
2222
+ const conflicts = [];
2223
+ const sortedRules = [...rules].sort((a, b) => b.priority - a.priority);
2224
+ for (let i = 0;i < sortedRules.length; i++) {
2225
+ const rule2 = sortedRules[i];
2226
+ if (!rule2.enabled)
2227
+ continue;
2228
+ for (let j = 0;j < i; j++) {
2229
+ const higherPriorityRule = sortedRules[j];
2230
+ if (!higherPriorityRule.enabled || !higherPriorityRule.stopOnMatch) {
2231
+ continue;
2232
+ }
2233
+ if (hashConditionGroup(rule2.conditions) === hashConditionGroup(higherPriorityRule.conditions)) {
2234
+ conflicts.push({
2235
+ type: "UNREACHABLE_RULE",
2236
+ severity: "warning",
2237
+ message: `Rule "${rule2.name}" may be unreachable because rule "${higherPriorityRule.name}" has higher priority and stops on match`,
2238
+ ruleIds: [rule2.id, higherPriorityRule.id],
2239
+ rules: [rule2, higherPriorityRule],
2240
+ details: {
2241
+ unreachableRule: rule2.id,
2242
+ blockingRule: higherPriorityRule.id,
2243
+ priorityDifference: higherPriorityRule.priority - rule2.priority
2244
+ }
2245
+ });
2246
+ }
2247
+ }
2248
+ }
2249
+ return conflicts;
2250
+ };
2251
+ var detectConflicts = (rules, options = {}) => {
2252
+ const opts = { ...DEFAULT_OPTIONS2, ...options };
2253
+ const conflicts = [];
2254
+ if (opts.checkDuplicateIds) {
2255
+ conflicts.push(...findDuplicateIds(rules));
2256
+ }
2257
+ if (opts.checkDuplicateConditions) {
2258
+ conflicts.push(...findDuplicateConditions(rules));
2259
+ }
2260
+ if (opts.checkOverlappingConditions) {
2261
+ conflicts.push(...findOverlappingConditions(rules));
2262
+ }
2263
+ if (opts.checkPriorityCollisions) {
2264
+ conflicts.push(...findPriorityCollisions(rules));
2265
+ }
2266
+ if (opts.checkUnreachableRules) {
2267
+ conflicts.push(...findUnreachableRules(rules));
2268
+ }
2269
+ return conflicts;
2270
+ };
2271
+ var hasConflicts = (rules, options = {}) => {
2272
+ return detectConflicts(rules, options).length > 0;
2273
+ };
2274
+ var hasErrors = (rules, options = {}) => {
2275
+ return detectConflicts(rules, options).some((c) => c.severity === "error");
2276
+ };
2277
+ var getConflictsByType = (conflicts, type) => {
2278
+ return conflicts.filter((c) => c.type === type);
2279
+ };
2280
+ var getConflictsBySeverity = (conflicts, severity) => {
2281
+ return conflicts.filter((c) => c.severity === severity);
2282
+ };
2283
+ var formatConflicts = (conflicts) => {
2284
+ if (conflicts.length === 0) {
2285
+ return "No conflicts detected";
2286
+ }
2287
+ const lines = [`Found ${conflicts.length} conflict(s):`];
2288
+ for (const conflict of conflicts) {
2289
+ const severityIcon = conflict.severity === "error" ? "[ERROR]" : conflict.severity === "warning" ? "[WARN]" : "[INFO]";
2290
+ lines.push(` ${severityIcon} ${conflict.type}: ${conflict.message}`);
2291
+ }
2292
+ return lines.join(`
2293
+ `);
2294
+ };
2295
+ // src/validation/integrity.ts
2296
+ import {
2297
+ isConditionGroup as isConditionGroup4
2298
+ } from "@f-o-t/condition-evaluator";
2299
+ var DEFAULT_OPTIONS3 = {
2300
+ checkCircularReferences: true,
2301
+ checkOrphanedRuleSets: true,
2302
+ checkMissingReferences: true,
2303
+ checkFieldConsistency: true
2304
+ };
2305
+ var createIssue = (code, message, severity, details) => ({
2306
+ code,
2307
+ message,
2308
+ severity,
2309
+ path: details?.path,
2310
+ ruleId: details?.ruleId,
2311
+ details: details?.extra
2312
+ });
2313
+ var collectAllFields = (condition) => {
2314
+ const fields = new Set;
2315
+ const traverse = (c) => {
2316
+ if (isConditionGroup4(c)) {
2317
+ for (const child of c.conditions) {
2318
+ traverse(child);
2319
+ }
2320
+ } else {
2321
+ fields.add(c.field);
2322
+ }
2323
+ };
2324
+ traverse(condition);
2325
+ return fields;
2326
+ };
2327
+ var checkDuplicateConditionIds = (condition, ruleId) => {
2328
+ const issues = [];
2329
+ const seenIds = new Map;
2330
+ const traverse = (c, path) => {
2331
+ const id = c.id;
2332
+ const count = seenIds.get(id) ?? 0;
2333
+ seenIds.set(id, count + 1);
2334
+ if (count > 0) {
2335
+ issues.push(createIssue("DUPLICATE_CONDITION_ID", `Duplicate condition ID "${id}" found within rule`, "error", { path, ruleId, extra: { conditionId: id } }));
2336
+ }
2337
+ if (isConditionGroup4(c)) {
2338
+ c.conditions.forEach((child, i) => {
2339
+ traverse(child, `${path}[${i}]`);
2340
+ });
2341
+ }
2342
+ };
2343
+ traverse(condition, "conditions");
2344
+ return issues;
2345
+ };
2346
+ var checkRuleIntegrity = (rule2, options) => {
2347
+ const issues = [];
2348
+ issues.push(...checkDuplicateConditionIds(rule2.conditions, rule2.id));
2349
+ if (rule2.priority < 0) {
2350
+ issues.push(createIssue("NEGATIVE_PRIORITY", `Rule "${rule2.name}" has negative priority: ${rule2.priority}`, "warning", { ruleId: rule2.id, extra: { priority: rule2.priority } }));
2351
+ }
2352
+ if (rule2.priority > 1000) {
2353
+ issues.push(createIssue("EXTREME_PRIORITY", `Rule "${rule2.name}" has very high priority: ${rule2.priority}`, "info", { ruleId: rule2.id, extra: { priority: rule2.priority } }));
2354
+ }
2355
+ if (rule2.consequences.length === 0) {
2356
+ issues.push(createIssue("NO_CONSEQUENCES", `Rule "${rule2.name}" has no consequences defined`, "warning", { ruleId: rule2.id }));
2357
+ }
2358
+ if (options.allowedCategories && rule2.category) {
2359
+ if (!options.allowedCategories.includes(rule2.category)) {
2360
+ issues.push(createIssue("INVALID_CATEGORY", `Rule "${rule2.name}" has invalid category: ${rule2.category}`, "error", {
2361
+ ruleId: rule2.id,
2362
+ extra: {
2363
+ category: rule2.category,
2364
+ allowedCategories: [...options.allowedCategories]
2365
+ }
2366
+ }));
2367
+ }
2368
+ }
2369
+ if (options.allowedTags) {
2370
+ const invalidTags = rule2.tags.filter((t) => !options.allowedTags.includes(t));
2371
+ if (invalidTags.length > 0) {
2372
+ issues.push(createIssue("INVALID_TAGS", `Rule "${rule2.name}" has invalid tags: ${invalidTags.join(", ")}`, "warning", {
2373
+ ruleId: rule2.id,
2374
+ extra: {
2375
+ invalidTags,
2376
+ allowedTags: [...options.allowedTags]
2377
+ }
2378
+ }));
2379
+ }
2380
+ }
2381
+ if (options.requiredFields) {
2382
+ const ruleFields = collectAllFields(rule2.conditions);
2383
+ const missingFields = options.requiredFields.filter((f) => !ruleFields.has(f));
2384
+ if (missingFields.length > 0) {
2385
+ issues.push(createIssue("MISSING_REQUIRED_FIELDS", `Rule "${rule2.name}" is missing required fields: ${missingFields.join(", ")}`, "warning", { ruleId: rule2.id, extra: { missingFields } }));
2386
+ }
2387
+ }
2388
+ return issues;
2389
+ };
2390
+ var checkRuleSetIntegrity = (ruleSet, rules) => {
2391
+ const issues = [];
2392
+ const ruleIds = new Set(rules.map((r) => r.id));
2393
+ for (const ruleId of ruleSet.ruleIds) {
2394
+ if (!ruleIds.has(ruleId)) {
2395
+ issues.push(createIssue("MISSING_RULE_REFERENCE", `RuleSet "${ruleSet.name}" references non-existent rule: ${ruleId}`, "error", { extra: { ruleSetId: ruleSet.id, missingRuleId: ruleId } }));
2396
+ }
2397
+ }
2398
+ if (ruleSet.ruleIds.length === 0) {
2399
+ issues.push(createIssue("EMPTY_RULESET", `RuleSet "${ruleSet.name}" contains no rules`, "warning", { extra: { ruleSetId: ruleSet.id } }));
2400
+ }
2401
+ const duplicateIds = ruleSet.ruleIds.filter((id, i) => ruleSet.ruleIds.indexOf(id) !== i);
2402
+ if (duplicateIds.length > 0) {
2403
+ issues.push(createIssue("DUPLICATE_RULESET_ENTRIES", `RuleSet "${ruleSet.name}" contains duplicate rule references: ${[...new Set(duplicateIds)].join(", ")}`, "warning", { extra: { ruleSetId: ruleSet.id, duplicateIds } }));
2404
+ }
2405
+ return issues;
2406
+ };
2407
+ var checkFieldConsistency = (rules) => {
2408
+ const issues = [];
2409
+ const fieldTypes = new Map;
2410
+ for (const rule2 of rules) {
2411
+ const traverse = (c) => {
2412
+ if (isConditionGroup4(c)) {
2413
+ for (const child of c.conditions) {
2414
+ traverse(child);
2415
+ }
2416
+ } else {
2417
+ const existing = fieldTypes.get(c.field) ?? [];
2418
+ existing.push({
2419
+ type: c.type,
2420
+ ruleId: rule2.id,
2421
+ ruleName: rule2.name
2422
+ });
2423
+ fieldTypes.set(c.field, existing);
2424
+ }
2425
+ };
2426
+ traverse(rule2.conditions);
2427
+ }
2428
+ for (const [field, types] of fieldTypes) {
2429
+ const uniqueTypes = [...new Set(types.map((t) => t.type))];
2430
+ if (uniqueTypes.length > 1) {
2431
+ issues.push(createIssue("INCONSISTENT_FIELD_TYPE", `Field "${field}" is used with different types: ${uniqueTypes.join(", ")}`, "warning", {
2432
+ extra: {
2433
+ field,
2434
+ types: uniqueTypes,
2435
+ rules: types.map((t) => ({
2436
+ id: t.ruleId,
2437
+ name: t.ruleName,
2438
+ type: t.type
2439
+ }))
2440
+ }
2441
+ }));
2442
+ }
2443
+ }
2444
+ return issues;
2445
+ };
2446
+ var checkIntegrity = (rules, ruleSets = [], options = {}) => {
2447
+ const opts = { ...DEFAULT_OPTIONS3, ...options };
2448
+ const issues = [];
2449
+ for (const rule2 of rules) {
2450
+ issues.push(...checkRuleIntegrity(rule2, opts));
2451
+ }
2452
+ for (const ruleSet of ruleSets) {
2453
+ issues.push(...checkRuleSetIntegrity(ruleSet, rules));
2454
+ }
2455
+ if (opts.checkFieldConsistency) {
2456
+ issues.push(...checkFieldConsistency(rules));
2457
+ }
2458
+ return {
2459
+ valid: issues.filter((i) => i.severity === "error").length === 0,
2460
+ issues
2461
+ };
2462
+ };
2463
+ var checkRuleFieldCoverage = (rules, expectedFields) => {
2464
+ const allFields = new Set;
2465
+ for (const rule2 of rules) {
2466
+ const ruleFields = collectAllFields(rule2.conditions);
2467
+ for (const field of ruleFields) {
2468
+ allFields.add(field);
2469
+ }
2470
+ }
2471
+ const expectedSet = new Set(expectedFields);
2472
+ const coveredFields = [...expectedFields].filter((f) => allFields.has(f));
2473
+ const uncoveredFields = [...expectedFields].filter((f) => !allFields.has(f));
2474
+ const extraFields = [...allFields].filter((f) => !expectedSet.has(f));
2475
+ return {
2476
+ coveredFields,
2477
+ uncoveredFields,
2478
+ extraFields,
2479
+ coveragePercentage: expectedFields.length > 0 ? coveredFields.length / expectedFields.length * 100 : 100
2480
+ };
2481
+ };
2482
+ var getUsedFields = (rules) => {
2483
+ const fields = new Set;
2484
+ for (const rule2 of rules) {
2485
+ const ruleFields = collectAllFields(rule2.conditions);
2486
+ for (const field of ruleFields) {
2487
+ fields.add(field);
2488
+ }
2489
+ }
2490
+ return [...fields].sort();
2491
+ };
2492
+ var getUsedOperators = (rules) => {
2493
+ const operators = [];
2494
+ const seen = new Set;
2495
+ for (const rule2 of rules) {
2496
+ const traverse = (c) => {
2497
+ if (isConditionGroup4(c)) {
2498
+ for (const child of c.conditions) {
2499
+ traverse(child);
2500
+ }
2501
+ } else {
2502
+ const key = `${c.field}:${c.operator}:${c.type}`;
2503
+ if (!seen.has(key)) {
2504
+ seen.add(key);
2505
+ operators.push({
2506
+ field: c.field,
2507
+ operator: c.operator,
2508
+ type: c.type
2509
+ });
2510
+ }
2511
+ }
2512
+ };
2513
+ traverse(rule2.conditions);
2514
+ }
2515
+ return operators;
2516
+ };
2517
+ var formatIntegrityResult = (result) => {
2518
+ if (result.valid && result.issues.length === 0) {
2519
+ return "Integrity check passed - no issues found";
2520
+ }
2521
+ const lines = [
2522
+ result.valid ? `Integrity check passed with ${result.issues.length} warning(s)` : `Integrity check failed with ${result.issues.filter((i) => i.severity === "error").length} error(s)`
2523
+ ];
2524
+ const grouped = {
2525
+ error: result.issues.filter((i) => i.severity === "error"),
2526
+ warning: result.issues.filter((i) => i.severity === "warning"),
2527
+ info: result.issues.filter((i) => i.severity === "info")
2528
+ };
2529
+ for (const [severity, issues] of Object.entries(grouped)) {
2530
+ if (issues.length > 0) {
2531
+ lines.push(`
2532
+ ${severity.toUpperCase()}S (${issues.length}):`);
2533
+ for (const issue of issues) {
2534
+ lines.push(` - [${issue.code}] ${issue.message}`);
2535
+ }
2536
+ }
2537
+ }
2538
+ return lines.join(`
2539
+ `);
2540
+ };
2541
+ // src/validation/schema.ts
2542
+ import {
2543
+ isConditionGroup as isConditionGroup5
2544
+ } from "@f-o-t/condition-evaluator";
2545
+ var DEFAULT_OPTIONS4 = {
2546
+ validateConditions: true,
2547
+ validateConsequences: true,
2548
+ strictMode: false
2549
+ };
2550
+ var createError = (path, message, code) => ({
2551
+ path,
2552
+ message,
2553
+ code
2554
+ });
2555
+ var validResult = () => ({
2556
+ valid: true,
2557
+ errors: []
2558
+ });
2559
+ var invalidResult = (errors) => ({
2560
+ valid: false,
2561
+ errors
2562
+ });
2563
+ var validateConditionStructure = (condition, path) => {
2564
+ const errors = [];
2565
+ if (isConditionGroup5(condition)) {
2566
+ if (!condition.id || typeof condition.id !== "string") {
2567
+ errors.push(createError(`${path}.id`, "Condition group must have a string id", "MISSING_GROUP_ID"));
2568
+ }
2569
+ if (!["AND", "OR"].includes(condition.operator)) {
2570
+ errors.push(createError(`${path}.operator`, `Invalid operator: ${condition.operator}. Must be "AND" or "OR"`, "INVALID_GROUP_OPERATOR"));
2571
+ }
2572
+ if (!Array.isArray(condition.conditions)) {
2573
+ errors.push(createError(`${path}.conditions`, "Condition group must have a conditions array", "MISSING_CONDITIONS_ARRAY"));
2574
+ } else if (condition.conditions.length === 0) {
2575
+ errors.push(createError(`${path}.conditions`, "Condition group must have at least one condition", "EMPTY_CONDITIONS_ARRAY"));
2576
+ } else {
2577
+ for (let i = 0;i < condition.conditions.length; i++) {
2578
+ const nestedErrors = validateConditionStructure(condition.conditions[i], `${path}.conditions[${i}]`);
2579
+ errors.push(...nestedErrors);
2580
+ }
2581
+ }
2582
+ } else {
2583
+ if (!condition.id || typeof condition.id !== "string") {
2584
+ errors.push(createError(`${path}.id`, "Condition must have a string id", "MISSING_CONDITION_ID"));
2585
+ }
2586
+ if (!condition.type || typeof condition.type !== "string") {
2587
+ errors.push(createError(`${path}.type`, "Condition must have a type", "MISSING_CONDITION_TYPE"));
2588
+ }
2589
+ if (!condition.field || typeof condition.field !== "string") {
2590
+ errors.push(createError(`${path}.field`, "Condition must have a field", "MISSING_CONDITION_FIELD"));
2591
+ }
2592
+ if (!condition.operator || typeof condition.operator !== "string") {
2593
+ errors.push(createError(`${path}.operator`, "Condition must have an operator", "MISSING_CONDITION_OPERATOR"));
2594
+ }
2595
+ }
2596
+ return errors;
2597
+ };
2598
+ var validateConsequenceStructure = (consequences, consequenceSchemas, strictMode = false) => {
2599
+ const errors = [];
2600
+ for (let i = 0;i < consequences.length; i++) {
2601
+ const consequence = consequences[i];
2602
+ const path = `consequences[${i}]`;
2603
+ if (!consequence.type || typeof consequence.type !== "string") {
2604
+ errors.push(createError(`${path}.type`, "Consequence must have a type", "MISSING_CONSEQUENCE_TYPE"));
2605
+ continue;
2606
+ }
2607
+ if (strictMode && consequenceSchemas) {
2608
+ const schema = consequenceSchemas[consequence.type];
2609
+ if (!schema) {
2610
+ errors.push(createError(`${path}.type`, `Unknown consequence type: ${consequence.type}`, "UNKNOWN_CONSEQUENCE_TYPE"));
2611
+ } else {
2612
+ const result = schema.safeParse(consequence.payload);
2613
+ if (!result.success) {
2614
+ for (const issue of result.error.issues) {
2615
+ errors.push(createError(`${path}.payload.${issue.path.join(".")}`, issue.message, "INVALID_CONSEQUENCE_PAYLOAD"));
2616
+ }
2617
+ }
2618
+ }
2619
+ }
2620
+ }
2621
+ return errors;
2622
+ };
2623
+ var validateRule = (rule2, options = {}) => {
2624
+ const opts = { ...DEFAULT_OPTIONS4, ...options };
2625
+ const errors = [];
2626
+ const schemaResult = RuleSchema.safeParse(rule2);
2627
+ if (!schemaResult.success) {
2628
+ for (const issue of schemaResult.error.issues) {
2629
+ errors.push(createError(issue.path.join("."), issue.message, "SCHEMA_VALIDATION_FAILED"));
2630
+ }
2631
+ return invalidResult(errors);
2632
+ }
2633
+ const validRule = schemaResult.data;
2634
+ if (opts.validateConditions) {
2635
+ const conditionErrors = validateConditionStructure(validRule.conditions, "conditions");
2636
+ errors.push(...conditionErrors);
2637
+ }
2638
+ if (opts.validateConsequences) {
2639
+ const consequenceErrors = validateConsequenceStructure(validRule.consequences, opts.consequenceSchemas, opts.strictMode);
2640
+ errors.push(...consequenceErrors);
2641
+ }
2642
+ return errors.length > 0 ? invalidResult(errors) : validResult();
2643
+ };
2644
+ var validateRules = (rules, options = {}) => {
2645
+ const errors = [];
2646
+ for (let i = 0;i < rules.length; i++) {
2647
+ const result = validateRule(rules[i], options);
2648
+ if (!result.valid) {
2649
+ for (const error of result.errors) {
2650
+ errors.push(createError(`rules[${i}].${error.path}`, error.message, error.code));
2651
+ }
2652
+ }
2653
+ }
2654
+ return errors.length > 0 ? invalidResult(errors) : validResult();
2655
+ };
2656
+ var validateRuleSet = (ruleSet) => {
2657
+ const errors = [];
2658
+ const schemaResult = RuleSetSchema.safeParse(ruleSet);
2659
+ if (!schemaResult.success) {
2660
+ for (const issue of schemaResult.error.issues) {
2661
+ errors.push(createError(issue.path.join("."), issue.message, "SCHEMA_VALIDATION_FAILED"));
2662
+ }
2663
+ return invalidResult(errors);
2664
+ }
2665
+ return validResult();
2666
+ };
2667
+ var validateConditions = (conditions2) => {
2668
+ const errors = validateConditionStructure(conditions2, "conditions");
2669
+ return errors.length > 0 ? invalidResult(errors) : validResult();
2670
+ };
2671
+ var parseRule = (rule2, options = {}) => {
2672
+ const result = validateRule(rule2, options);
2673
+ if (!result.valid) {
2674
+ throw new Error(`Invalid rule: ${result.errors.map((e) => e.message).join(", ")}`);
2675
+ }
2676
+ return rule2;
2677
+ };
2678
+ var safeParseRule = (rule2, options = {}) => {
2679
+ const result = validateRule(rule2, options);
2680
+ if (!result.valid) {
2681
+ return { success: false, errors: result.errors };
2682
+ }
2683
+ return { success: true, data: rule2 };
2684
+ };
2685
+ var createRuleValidator = (consequenceSchemas, defaultOptions = {}) => {
2686
+ return {
2687
+ validate: (rule2, options = {}) => validateRule(rule2, {
2688
+ ...defaultOptions,
2689
+ ...options,
2690
+ consequenceSchemas
2691
+ }),
2692
+ parse: (rule2, options = {}) => parseRule(rule2, {
2693
+ ...defaultOptions,
2694
+ ...options,
2695
+ consequenceSchemas
2696
+ }),
2697
+ safeParse: (rule2, options = {}) => safeParseRule(rule2, {
2698
+ ...defaultOptions,
2699
+ ...options,
2700
+ consequenceSchemas
2701
+ })
2702
+ };
2703
+ };
2704
+ // src/versioning/version-store.ts
2705
+ var createVersionStore = () => ({
2706
+ histories: new Map
2707
+ });
2708
+ var addVersion = (store, rule2, changeType, options = {}) => {
2709
+ const histories = new Map(store.histories);
2710
+ const existingHistory = histories.get(rule2.id);
2711
+ const currentVersion = existingHistory ? existingHistory.currentVersion + 1 : 1;
2712
+ const version = {
2713
+ versionId: generateId(),
2714
+ ruleId: rule2.id,
2715
+ version: currentVersion,
2716
+ rule: { ...rule2 },
2717
+ createdAt: new Date,
2718
+ createdBy: options.createdBy,
2719
+ comment: options.comment,
2720
+ changeType
2721
+ };
2722
+ const newHistory = {
2723
+ ruleId: rule2.id,
2724
+ currentVersion,
2725
+ versions: existingHistory ? [...existingHistory.versions, version] : [version]
2726
+ };
2727
+ histories.set(rule2.id, newHistory);
2728
+ return { histories };
2729
+ };
2730
+ var getHistory = (store, ruleId) => {
2731
+ return store.histories.get(ruleId);
2732
+ };
2733
+ var getVersion = (store, ruleId, version) => {
2734
+ const history = store.histories.get(ruleId);
2735
+ if (!history)
2736
+ return;
2737
+ return history.versions.find((v) => v.version === version);
2738
+ };
2739
+ var getLatestVersion = (store, ruleId) => {
2740
+ const history = store.histories.get(ruleId);
2741
+ if (!history || history.versions.length === 0)
2742
+ return;
2743
+ return history.versions[history.versions.length - 1];
2744
+ };
2745
+ var getAllVersions = (store, ruleId) => {
2746
+ const history = store.histories.get(ruleId);
2747
+ return history?.versions ?? [];
2748
+ };
2749
+ var rollbackToVersion = (store, ruleId, targetVersion, options = {}) => {
2750
+ const targetVersionRecord = getVersion(store, ruleId, targetVersion);
2751
+ if (!targetVersionRecord) {
2752
+ return { store, rule: undefined };
2753
+ }
2754
+ const rolledBackRule = {
2755
+ ...targetVersionRecord.rule,
2756
+ updatedAt: new Date
2757
+ };
2758
+ const newStore = addVersion(store, rolledBackRule, "update", {
2759
+ createdBy: options.createdBy,
2760
+ comment: options.comment ?? `Rollback to version ${targetVersion}`
2761
+ });
2762
+ return { store: newStore, rule: rolledBackRule };
2763
+ };
2764
+ var compareVersions = (store, ruleId, version1, version2) => {
2765
+ const v1 = getVersion(store, ruleId, version1);
2766
+ const v2 = getVersion(store, ruleId, version2);
2767
+ if (!v1 || !v2) {
2768
+ return null;
2769
+ }
2770
+ const differences = [];
2771
+ const compareFields = [
2772
+ "name",
2773
+ "description",
2774
+ "priority",
2775
+ "enabled",
2776
+ "stopOnMatch",
2777
+ "category"
2778
+ ];
2779
+ for (const field of compareFields) {
2780
+ const oldValue = v1.rule[field];
2781
+ const newValue = v2.rule[field];
2782
+ if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {
2783
+ differences.push({ field, oldValue, newValue });
2784
+ }
2785
+ }
2786
+ if (JSON.stringify(v1.rule.conditions) !== JSON.stringify(v2.rule.conditions)) {
2787
+ differences.push({
2788
+ field: "conditions",
2789
+ oldValue: v1.rule.conditions,
2790
+ newValue: v2.rule.conditions
2791
+ });
2792
+ }
2793
+ if (JSON.stringify(v1.rule.consequences) !== JSON.stringify(v2.rule.consequences)) {
2794
+ differences.push({
2795
+ field: "consequences",
2796
+ oldValue: v1.rule.consequences,
2797
+ newValue: v2.rule.consequences
2798
+ });
2799
+ }
2800
+ if (JSON.stringify(v1.rule.tags) !== JSON.stringify(v2.rule.tags)) {
2801
+ differences.push({
2802
+ field: "tags",
2803
+ oldValue: v1.rule.tags,
2804
+ newValue: v2.rule.tags
2805
+ });
2806
+ }
2807
+ return { version1: v1, version2: v2, differences };
2808
+ };
2809
+ var getVersionsByDateRange = (store, startDate, endDate) => {
2810
+ const versions = [];
2811
+ for (const history of store.histories.values()) {
2812
+ for (const version of history.versions) {
2813
+ if (version.createdAt >= startDate && version.createdAt <= endDate) {
2814
+ versions.push(version);
2815
+ }
2816
+ }
2817
+ }
2818
+ return versions.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
2819
+ };
2820
+ var getVersionsByChangeType = (store, changeType) => {
2821
+ const versions = [];
2822
+ for (const history of store.histories.values()) {
2823
+ for (const version of history.versions) {
2824
+ if (version.changeType === changeType) {
2825
+ versions.push(version);
2826
+ }
2827
+ }
2828
+ }
2829
+ return versions;
2830
+ };
2831
+ var pruneOldVersions = (store, keepCount) => {
2832
+ const histories = new Map;
2833
+ for (const [ruleId, history] of store.histories) {
2834
+ const prunedVersions = history.versions.length > keepCount ? history.versions.slice(-keepCount) : history.versions;
2835
+ histories.set(ruleId, {
2836
+ ruleId,
2837
+ currentVersion: history.currentVersion,
2838
+ versions: prunedVersions
2839
+ });
2840
+ }
2841
+ return { histories };
2842
+ };
2843
+ var formatVersionHistory = (history) => {
2844
+ const lines = [
2845
+ `=== Version History for Rule: ${history.ruleId} ===`,
2846
+ `Current Version: ${history.currentVersion}`,
2847
+ `Total Versions: ${history.versions.length}`,
2848
+ ""
2849
+ ];
2850
+ for (const version of history.versions) {
2851
+ lines.push(`v${version.version} - ${version.changeType} (${version.createdAt.toISOString()})`);
2852
+ if (version.createdBy) {
2853
+ lines.push(` By: ${version.createdBy}`);
2854
+ }
2855
+ if (version.comment) {
2856
+ lines.push(` Comment: ${version.comment}`);
2857
+ }
2858
+ }
2859
+ return lines.join(`
2860
+ `);
2861
+ };
2862
+ export {
2863
+ withTimeout,
2864
+ whatIf,
2865
+ validateRules,
2866
+ validateRuleSet,
2867
+ validateRule,
2868
+ validateConditions,
2869
+ updateRule,
2870
+ tap,
2871
+ str,
2872
+ sortRules,
2873
+ sortByUpdatedAt,
2874
+ sortByPriority,
2875
+ sortByName,
2876
+ sortByCreatedAt,
2877
+ simulateSingleRule,
2878
+ simulate,
2879
+ serializeRuleSet,
2880
+ serializeRule,
2881
+ safeParseRule,
2882
+ rule,
2883
+ rollbackToVersion,
2884
+ resetBuilderIds,
2885
+ removeRuleSet,
2886
+ removeRule,
2887
+ pruneOldVersions,
2888
+ pipe,
2889
+ parseRule,
2890
+ or,
2891
+ num,
2892
+ mergeRuleSets,
2893
+ measureTimeAsync,
2894
+ measureTime,
2895
+ importRules,
2896
+ importFromJson,
2897
+ identity,
2898
+ hashRules,
2899
+ hashContext,
2900
+ hasErrors,
2901
+ hasConflicts,
2902
+ groupRules,
2903
+ groupByPriority,
2904
+ groupByEnabled,
2905
+ groupByCustom,
2906
+ groupByCategory,
2907
+ getVersionsByDateRange,
2908
+ getVersionsByChangeType,
2909
+ getVersion,
2910
+ getUsedOperators,
2911
+ getUsedFields,
2912
+ getRulesInSet,
2913
+ getRulesByTags,
2914
+ getRulesByTag,
2915
+ getRulesByPriorityRange,
2916
+ getRulesByPriority,
2917
+ getRulesByFields,
2918
+ getRulesByField,
2919
+ getRulesByCategory,
2920
+ getRules,
2921
+ getRuleSets,
2922
+ getRuleSet,
2923
+ getRuleById,
2924
+ getRule,
2925
+ getLatestVersion,
2926
+ getIndexStats,
2927
+ getHistory,
2928
+ getConflictsByType,
2929
+ getConflictsBySeverity,
2930
+ getAllVersions,
2931
+ generateId,
2932
+ formatVersionHistory,
2933
+ formatSimulationResult,
2934
+ formatRuleSetAnalysis,
2935
+ formatIntegrityResult,
2936
+ formatConflicts,
2937
+ findRulesAffectedByContextChange,
2938
+ findMostComplexRules,
2939
+ findLeastUsedFields,
2940
+ filterRulesForContext,
2941
+ filterRules,
2942
+ filterByTags,
2943
+ filterByIds,
2944
+ filterByEnabled,
2945
+ filterByCategory,
2946
+ exportToJson,
2947
+ exportRules,
2948
+ evaluateRules,
2949
+ evaluateRule,
2950
+ enableRule,
2951
+ disableRule,
2952
+ diffRuleSets,
2953
+ detectConflicts,
2954
+ deserializeRuleSet,
2955
+ deserializeRule,
2956
+ delay,
2957
+ date,
2958
+ createVersionStore,
2959
+ createRuleValidator,
2960
+ createRule,
2961
+ createNoopCache,
2962
+ createInitialState,
2963
+ createInitialRuleStats,
2964
+ createInitialOptimizerState,
2965
+ createEngine,
2966
+ createCache,
2967
+ conditions,
2968
+ compose,
2969
+ compareVersions,
2970
+ cloneState,
2971
+ cloneRule,
2972
+ clearRules,
2973
+ checkRuleFieldCoverage,
2974
+ checkIntegrity,
2975
+ buildIndex,
2976
+ bool,
2977
+ batchSimulate,
2978
+ arr,
2979
+ any,
2980
+ and,
2981
+ analyzeRuleSet,
2982
+ analyzeRuleComplexity,
2983
+ analyzeOptimizations,
2984
+ analyzeOperatorUsage,
2985
+ analyzeFieldUsage,
2986
+ analyzeConsequenceUsage,
2987
+ always,
2988
+ all,
2989
+ addVersion,
2990
+ addRules,
2991
+ addRuleSet,
2992
+ addRule,
2993
+ RuleSetSchema,
2994
+ RuleSchema,
2995
+ DEFAULT_VERSIONING_CONFIG,
2996
+ DEFAULT_VALIDATION_CONFIG,
2997
+ DEFAULT_ENGINE_CONFIG,
2998
+ DEFAULT_CACHE_CONFIG
2999
+ };