@f-o-t/rules-engine 2.0.2 → 3.0.1

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