@f-o-t/rules-engine 3.0.0 → 3.0.5

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.
Files changed (105) hide show
  1. package/dist/analyzer/analysis.d.ts +72 -0
  2. package/dist/analyzer/analysis.d.ts.map +1 -0
  3. package/dist/builder/conditions.d.ts +29 -0
  4. package/dist/builder/conditions.d.ts.map +1 -0
  5. package/dist/builder/rule.d.ts +40 -0
  6. package/dist/builder/rule.d.ts.map +1 -0
  7. package/dist/cache/cache.d.ts +21 -0
  8. package/dist/cache/cache.d.ts.map +1 -0
  9. package/dist/cache/noop.d.ts +3 -0
  10. package/dist/cache/noop.d.ts.map +1 -0
  11. package/dist/core/evaluate.d.ts +21 -0
  12. package/dist/core/evaluate.d.ts.map +1 -0
  13. package/dist/core/filter.d.ts +8 -0
  14. package/dist/core/filter.d.ts.map +1 -0
  15. package/dist/core/group.d.ts +10 -0
  16. package/dist/core/group.d.ts.map +1 -0
  17. package/dist/core/sort.d.ts +14 -0
  18. package/dist/core/sort.d.ts.map +1 -0
  19. package/dist/engine/engine.d.ts +27 -0
  20. package/dist/engine/engine.d.ts.map +1 -0
  21. package/dist/engine/hooks.d.ts +16 -0
  22. package/dist/engine/hooks.d.ts.map +1 -0
  23. package/dist/engine/state.d.ts +19 -0
  24. package/dist/engine/state.d.ts.map +1 -0
  25. package/dist/index.d.ts +33 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +3083 -0
  28. package/dist/index.js.map +37 -0
  29. package/dist/optimizer/index-builder.d.ts +48 -0
  30. package/dist/optimizer/index-builder.d.ts.map +1 -0
  31. package/dist/serialization/serializer.d.ts +79 -0
  32. package/dist/serialization/serializer.d.ts.map +1 -0
  33. package/dist/simulation/simulator.d.ts +65 -0
  34. package/dist/simulation/simulator.d.ts.map +1 -0
  35. package/dist/types/config.d.ts +87 -0
  36. package/dist/types/config.d.ts.map +1 -0
  37. package/dist/types/consequence.d.ts +21 -0
  38. package/dist/types/consequence.d.ts.map +1 -0
  39. package/dist/types/evaluation.d.ts +70 -0
  40. package/dist/types/evaluation.d.ts.map +1 -0
  41. package/dist/types/rule.d.ts +114 -0
  42. package/dist/types/rule.d.ts.map +1 -0
  43. package/dist/types/state.d.ts +65 -0
  44. package/dist/types/state.d.ts.map +1 -0
  45. package/dist/utils/conditions.d.ts +24 -0
  46. package/dist/utils/conditions.d.ts.map +1 -0
  47. package/dist/utils/hash.d.ts +3 -0
  48. package/dist/utils/hash.d.ts.map +1 -0
  49. package/dist/utils/id.d.ts +2 -0
  50. package/dist/utils/id.d.ts.map +1 -0
  51. package/dist/utils/time.d.ts +8 -0
  52. package/dist/utils/time.d.ts.map +1 -0
  53. package/dist/validation/conflicts.d.ts +25 -0
  54. package/dist/validation/conflicts.d.ts.map +1 -0
  55. package/dist/validation/integrity.d.ts +38 -0
  56. package/dist/validation/integrity.d.ts.map +1 -0
  57. package/dist/validation/schema.d.ts +69 -0
  58. package/dist/validation/schema.d.ts.map +1 -0
  59. package/dist/versioning/version-store.d.ts +50 -0
  60. package/dist/versioning/version-store.d.ts.map +1 -0
  61. package/package.json +35 -31
  62. package/CHANGELOG.md +0 -168
  63. package/__tests__/builder.test.ts +0 -363
  64. package/__tests__/cache.test.ts +0 -130
  65. package/__tests__/config.test.ts +0 -35
  66. package/__tests__/engine.test.ts +0 -1213
  67. package/__tests__/evaluate.test.ts +0 -339
  68. package/__tests__/exports.test.ts +0 -30
  69. package/__tests__/filter-sort.test.ts +0 -303
  70. package/__tests__/integration.test.ts +0 -419
  71. package/__tests__/money-integration.test.ts +0 -149
  72. package/__tests__/validation.test.ts +0 -862
  73. package/biome.json +0 -39
  74. package/docs/MIGRATION-v3.md +0 -118
  75. package/fot.config.ts +0 -5
  76. package/src/analyzer/analysis.ts +0 -401
  77. package/src/builder/conditions.ts +0 -321
  78. package/src/builder/rule.ts +0 -192
  79. package/src/cache/cache.ts +0 -135
  80. package/src/cache/noop.ts +0 -20
  81. package/src/core/evaluate.ts +0 -185
  82. package/src/core/filter.ts +0 -85
  83. package/src/core/group.ts +0 -103
  84. package/src/core/sort.ts +0 -90
  85. package/src/engine/engine.ts +0 -462
  86. package/src/engine/hooks.ts +0 -235
  87. package/src/engine/state.ts +0 -322
  88. package/src/index.ts +0 -303
  89. package/src/optimizer/index-builder.ts +0 -381
  90. package/src/serialization/serializer.ts +0 -408
  91. package/src/simulation/simulator.ts +0 -359
  92. package/src/types/config.ts +0 -184
  93. package/src/types/consequence.ts +0 -38
  94. package/src/types/evaluation.ts +0 -87
  95. package/src/types/rule.ts +0 -112
  96. package/src/types/state.ts +0 -116
  97. package/src/utils/conditions.ts +0 -108
  98. package/src/utils/hash.ts +0 -30
  99. package/src/utils/id.ts +0 -6
  100. package/src/utils/time.ts +0 -42
  101. package/src/validation/conflicts.ts +0 -440
  102. package/src/validation/integrity.ts +0 -473
  103. package/src/validation/schema.ts +0 -386
  104. package/src/versioning/version-store.ts +0 -337
  105. package/tsconfig.json +0 -29
@@ -1,381 +0,0 @@
1
- import type {
2
- ConsequenceDefinitions,
3
- DefaultConsequences,
4
- } from "../types/consequence";
5
- import type { Rule } from "../types/rule";
6
- import { collectConditionFields } from "../utils/conditions";
7
-
8
- export type FieldIndex<
9
- TContext = unknown,
10
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
11
- > = Map<string, ReadonlyArray<Rule<TContext, TConsequences>>>;
12
-
13
- export type TagIndex<
14
- TContext = unknown,
15
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
16
- > = Map<string, ReadonlyArray<Rule<TContext, TConsequences>>>;
17
-
18
- export type CategoryIndex<
19
- TContext = unknown,
20
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
21
- > = Map<string, ReadonlyArray<Rule<TContext, TConsequences>>>;
22
-
23
- export type PriorityIndex<
24
- TContext = unknown,
25
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
26
- > = Map<number, ReadonlyArray<Rule<TContext, TConsequences>>>;
27
-
28
- export type RuleIndex<
29
- TContext = unknown,
30
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
31
- > = {
32
- readonly byField: FieldIndex<TContext, TConsequences>;
33
- readonly byTag: TagIndex<TContext, TConsequences>;
34
- readonly byCategory: CategoryIndex<TContext, TConsequences>;
35
- readonly byPriority: PriorityIndex<TContext, TConsequences>;
36
- readonly byId: Map<string, Rule<TContext, TConsequences>>;
37
- readonly sortedByPriority: ReadonlyArray<Rule<TContext, TConsequences>>;
38
- };
39
-
40
- export type IndexOptions = {
41
- readonly indexByField?: boolean;
42
- readonly indexByTag?: boolean;
43
- readonly indexByCategory?: boolean;
44
- readonly indexByPriority?: boolean;
45
- };
46
-
47
- const DEFAULT_OPTIONS: IndexOptions = {
48
- indexByField: true,
49
- indexByTag: true,
50
- indexByCategory: true,
51
- indexByPriority: true,
52
- };
53
-
54
- export const buildIndex = <
55
- TContext = unknown,
56
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
57
- >(
58
- rules: ReadonlyArray<Rule<TContext, TConsequences>>,
59
- options: IndexOptions = {},
60
- ): RuleIndex<TContext, TConsequences> => {
61
- const opts = { ...DEFAULT_OPTIONS, ...options };
62
-
63
- const byId = new Map<string, Rule<TContext, TConsequences>>();
64
- const byField = new Map<string, Rule<TContext, TConsequences>[]>();
65
- const byTag = new Map<string, Rule<TContext, TConsequences>[]>();
66
- const byCategory = new Map<string, Rule<TContext, TConsequences>[]>();
67
- const byPriority = new Map<number, Rule<TContext, TConsequences>[]>();
68
-
69
- for (const rule of rules) {
70
- byId.set(rule.id, rule);
71
-
72
- if (opts.indexByField) {
73
- const fields = collectConditionFields(rule.conditions);
74
- for (const field of fields) {
75
- const existing = byField.get(field) ?? [];
76
- existing.push(rule);
77
- byField.set(field, existing);
78
- }
79
- }
80
-
81
- if (opts.indexByTag) {
82
- for (const tag of rule.tags) {
83
- const existing = byTag.get(tag) ?? [];
84
- existing.push(rule);
85
- byTag.set(tag, existing);
86
- }
87
- }
88
-
89
- if (opts.indexByCategory && rule.category) {
90
- const existing = byCategory.get(rule.category) ?? [];
91
- existing.push(rule);
92
- byCategory.set(rule.category, existing);
93
- }
94
-
95
- if (opts.indexByPriority) {
96
- const existing = byPriority.get(rule.priority) ?? [];
97
- existing.push(rule);
98
- byPriority.set(rule.priority, existing);
99
- }
100
- }
101
-
102
- const sortedByPriority = [...rules].sort((a, b) => b.priority - a.priority);
103
-
104
- return {
105
- byField: byField as FieldIndex<TContext, TConsequences>,
106
- byTag: byTag as TagIndex<TContext, TConsequences>,
107
- byCategory: byCategory as CategoryIndex<TContext, TConsequences>,
108
- byPriority: byPriority as PriorityIndex<TContext, TConsequences>,
109
- byId,
110
- sortedByPriority,
111
- };
112
- };
113
-
114
- export const getRulesByField = <
115
- TContext = unknown,
116
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
117
- >(
118
- index: RuleIndex<TContext, TConsequences>,
119
- field: string,
120
- ): ReadonlyArray<Rule<TContext, TConsequences>> => {
121
- return index.byField.get(field) ?? [];
122
- };
123
-
124
- export const getRulesByFields = <
125
- TContext = unknown,
126
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
127
- >(
128
- index: RuleIndex<TContext, TConsequences>,
129
- fields: ReadonlyArray<string>,
130
- mode: "any" | "all" = "any",
131
- ): ReadonlyArray<Rule<TContext, TConsequences>> => {
132
- if (fields.length === 0) return [];
133
-
134
- const ruleSets = fields.map((f) => new Set(getRulesByField(index, f)));
135
-
136
- if (mode === "any") {
137
- const combined = new Set<Rule<TContext, TConsequences>>();
138
- for (const ruleSet of ruleSets) {
139
- for (const rule of ruleSet) {
140
- combined.add(rule);
141
- }
142
- }
143
- return [...combined];
144
- }
145
-
146
- const [first, ...rest] = ruleSets;
147
- if (!first) return [];
148
-
149
- return [...first].filter((rule) => rest.every((set) => set.has(rule)));
150
- };
151
-
152
- export const getRulesByTag = <
153
- TContext = unknown,
154
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
155
- >(
156
- index: RuleIndex<TContext, TConsequences>,
157
- tag: string,
158
- ): ReadonlyArray<Rule<TContext, TConsequences>> => {
159
- return index.byTag.get(tag) ?? [];
160
- };
161
-
162
- export const getRulesByTags = <
163
- TContext = unknown,
164
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
165
- >(
166
- index: RuleIndex<TContext, TConsequences>,
167
- tags: ReadonlyArray<string>,
168
- mode: "any" | "all" = "any",
169
- ): ReadonlyArray<Rule<TContext, TConsequences>> => {
170
- if (tags.length === 0) return [];
171
-
172
- const ruleSets = tags.map((t) => new Set(getRulesByTag(index, t)));
173
-
174
- if (mode === "any") {
175
- const combined = new Set<Rule<TContext, TConsequences>>();
176
- for (const ruleSet of ruleSets) {
177
- for (const rule of ruleSet) {
178
- combined.add(rule);
179
- }
180
- }
181
- return [...combined];
182
- }
183
-
184
- const [first, ...rest] = ruleSets;
185
- if (!first) return [];
186
-
187
- return [...first].filter((rule) => rest.every((set) => set.has(rule)));
188
- };
189
-
190
- export const getRulesByCategory = <
191
- TContext = unknown,
192
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
193
- >(
194
- index: RuleIndex<TContext, TConsequences>,
195
- category: string,
196
- ): ReadonlyArray<Rule<TContext, TConsequences>> => {
197
- return index.byCategory.get(category) ?? [];
198
- };
199
-
200
- export const getRulesByPriority = <
201
- TContext = unknown,
202
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
203
- >(
204
- index: RuleIndex<TContext, TConsequences>,
205
- priority: number,
206
- ): ReadonlyArray<Rule<TContext, TConsequences>> => {
207
- return index.byPriority.get(priority) ?? [];
208
- };
209
-
210
- export const getRulesByPriorityRange = <
211
- TContext = unknown,
212
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
213
- >(
214
- index: RuleIndex<TContext, TConsequences>,
215
- minPriority: number,
216
- maxPriority: number,
217
- ): ReadonlyArray<Rule<TContext, TConsequences>> => {
218
- return index.sortedByPriority.filter(
219
- (rule) => rule.priority >= minPriority && rule.priority <= maxPriority,
220
- );
221
- };
222
-
223
- export const getRuleById = <
224
- TContext = unknown,
225
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
226
- >(
227
- index: RuleIndex<TContext, TConsequences>,
228
- id: string,
229
- ): Rule<TContext, TConsequences> | undefined => {
230
- return index.byId.get(id);
231
- };
232
-
233
- export const filterRulesForContext = <
234
- TContext = unknown,
235
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
236
- >(
237
- index: RuleIndex<TContext, TConsequences>,
238
- contextFields: ReadonlyArray<string>,
239
- ): ReadonlyArray<Rule<TContext, TConsequences>> => {
240
- const relevantRules = new Set<Rule<TContext, TConsequences>>();
241
-
242
- for (const field of contextFields) {
243
- const rules = index.byField.get(field);
244
- if (rules) {
245
- for (const rule of rules) {
246
- relevantRules.add(rule);
247
- }
248
- }
249
- }
250
-
251
- return [...relevantRules].sort((a, b) => b.priority - a.priority);
252
- };
253
-
254
- export type OptimizationSuggestion = {
255
- readonly type: "INDEX" | "CACHE" | "MERGE" | "SIMPLIFY" | "REORDER";
256
- readonly severity: "high" | "medium" | "low";
257
- readonly message: string;
258
- readonly ruleIds?: ReadonlyArray<string>;
259
- readonly details?: Readonly<Record<string, unknown>>;
260
- };
261
-
262
- export const analyzeOptimizations = <
263
- TContext = unknown,
264
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
265
- >(
266
- rules: ReadonlyArray<Rule<TContext, TConsequences>>,
267
- ): ReadonlyArray<OptimizationSuggestion> => {
268
- const suggestions: OptimizationSuggestion[] = [];
269
-
270
- const fieldUsage = new Map<string, number>();
271
- for (const rule of rules) {
272
- const fields = collectConditionFields(rule.conditions);
273
- for (const field of fields) {
274
- fieldUsage.set(field, (fieldUsage.get(field) ?? 0) + 1);
275
- }
276
- }
277
-
278
- const frequentFields = [...fieldUsage.entries()]
279
- .filter(([, count]) => count > rules.length * 0.5)
280
- .map(([field]) => field);
281
-
282
- if (frequentFields.length > 0) {
283
- suggestions.push({
284
- type: "INDEX",
285
- severity: "medium",
286
- message: `Consider creating indexes for frequently used fields: ${frequentFields.join(", ")}`,
287
- details: {
288
- fields: frequentFields,
289
- usagePercentage: frequentFields.map((f) => ({
290
- field: f,
291
- percentage: ((fieldUsage.get(f) ?? 0) / rules.length) * 100,
292
- })),
293
- },
294
- });
295
- }
296
-
297
- const priorityGroups = new Map<number, Rule<TContext, TConsequences>[]>();
298
- for (const rule of rules) {
299
- const existing = priorityGroups.get(rule.priority) ?? [];
300
- existing.push(rule);
301
- priorityGroups.set(rule.priority, existing);
302
- }
303
-
304
- const largePriorityGroups = [...priorityGroups.entries()].filter(
305
- ([, group]) => group.length > 5,
306
- );
307
-
308
- if (largePriorityGroups.length > 0) {
309
- for (const [priority, group] of largePriorityGroups) {
310
- suggestions.push({
311
- type: "REORDER",
312
- severity: "low",
313
- message: `${group.length} rules share priority ${priority}. Consider differentiating priorities for more predictable execution order.`,
314
- ruleIds: group.map((r) => r.id),
315
- details: { priority, count: group.length },
316
- });
317
- }
318
- }
319
-
320
- const disabledRules = rules.filter((r) => !r.enabled);
321
- if (disabledRules.length > rules.length * 0.3) {
322
- suggestions.push({
323
- type: "SIMPLIFY",
324
- severity: "low",
325
- message: `${disabledRules.length} rules (${((disabledRules.length / rules.length) * 100).toFixed(1)}%) are disabled. Consider removing unused rules.`,
326
- ruleIds: disabledRules.map((r) => r.id),
327
- details: {
328
- disabledCount: disabledRules.length,
329
- totalCount: rules.length,
330
- },
331
- });
332
- }
333
-
334
- if (rules.length > 100) {
335
- suggestions.push({
336
- type: "CACHE",
337
- severity: "high",
338
- message: `Rule set contains ${rules.length} rules. Enable caching for better performance.`,
339
- details: { ruleCount: rules.length },
340
- });
341
- }
342
-
343
- return suggestions;
344
- };
345
-
346
- export const getIndexStats = <
347
- TContext = unknown,
348
- TConsequences extends ConsequenceDefinitions = DefaultConsequences,
349
- >(
350
- index: RuleIndex<TContext, TConsequences>,
351
- ): {
352
- totalRules: number;
353
- uniqueFields: number;
354
- uniqueTags: number;
355
- uniqueCategories: number;
356
- uniquePriorities: number;
357
- averageRulesPerField: number;
358
- averageRulesPerTag: number;
359
- } => {
360
- const totalRules = index.byId.size;
361
-
362
- const fieldRuleCounts = [...index.byField.values()].map((r) => r.length);
363
- const tagRuleCounts = [...index.byTag.values()].map((r) => r.length);
364
-
365
- return {
366
- totalRules,
367
- uniqueFields: index.byField.size,
368
- uniqueTags: index.byTag.size,
369
- uniqueCategories: index.byCategory.size,
370
- uniquePriorities: index.byPriority.size,
371
- averageRulesPerField:
372
- fieldRuleCounts.length > 0
373
- ? fieldRuleCounts.reduce((a, b) => a + b, 0) /
374
- fieldRuleCounts.length
375
- : 0,
376
- averageRulesPerTag:
377
- tagRuleCounts.length > 0
378
- ? tagRuleCounts.reduce((a, b) => a + b, 0) / tagRuleCounts.length
379
- : 0,
380
- };
381
- };