@real1ty-obsidian-plugins/utils 2.4.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/core/evaluator/base.d.ts +22 -0
  2. package/dist/core/evaluator/base.d.ts.map +1 -0
  3. package/dist/core/evaluator/base.js +52 -0
  4. package/dist/core/evaluator/base.js.map +1 -0
  5. package/dist/core/evaluator/color.d.ts +19 -0
  6. package/dist/core/evaluator/color.d.ts.map +1 -0
  7. package/dist/core/evaluator/color.js +25 -0
  8. package/dist/core/evaluator/color.js.map +1 -0
  9. package/dist/core/evaluator/excluded.d.ts +32 -0
  10. package/dist/core/evaluator/excluded.d.ts.map +1 -0
  11. package/dist/core/evaluator/excluded.js +41 -0
  12. package/dist/core/evaluator/excluded.js.map +1 -0
  13. package/dist/core/evaluator/filter.d.ts +15 -0
  14. package/dist/core/evaluator/filter.d.ts.map +1 -0
  15. package/dist/core/evaluator/filter.js +27 -0
  16. package/dist/core/evaluator/filter.js.map +1 -0
  17. package/dist/core/evaluator/included.d.ts +36 -0
  18. package/dist/core/evaluator/included.d.ts.map +1 -0
  19. package/dist/core/evaluator/included.js +51 -0
  20. package/dist/core/evaluator/included.js.map +1 -0
  21. package/dist/core/evaluator/index.d.ts +6 -0
  22. package/dist/core/evaluator/index.d.ts.map +1 -0
  23. package/dist/core/evaluator/index.js +6 -0
  24. package/dist/core/evaluator/index.js.map +1 -0
  25. package/dist/core/expression-utils.d.ts +17 -0
  26. package/dist/core/expression-utils.d.ts.map +1 -0
  27. package/dist/core/expression-utils.js +40 -0
  28. package/dist/core/expression-utils.js.map +1 -0
  29. package/dist/core/frontmatter-value.d.ts +143 -0
  30. package/dist/core/frontmatter-value.d.ts.map +1 -0
  31. package/dist/core/frontmatter-value.js +408 -0
  32. package/dist/core/frontmatter-value.js.map +1 -0
  33. package/dist/core/index.d.ts +3 -1
  34. package/dist/core/index.d.ts.map +1 -1
  35. package/dist/core/index.js +3 -1
  36. package/dist/core/index.js.map +1 -1
  37. package/dist/file/index.d.ts +1 -0
  38. package/dist/file/index.d.ts.map +1 -1
  39. package/dist/file/index.js +1 -0
  40. package/dist/file/index.js.map +1 -1
  41. package/dist/file/link-parser.d.ts +26 -0
  42. package/dist/file/link-parser.d.ts.map +1 -1
  43. package/dist/file/link-parser.js +59 -0
  44. package/dist/file/link-parser.js.map +1 -1
  45. package/dist/file/property-utils.d.ts +55 -0
  46. package/dist/file/property-utils.d.ts.map +1 -0
  47. package/dist/file/property-utils.js +90 -0
  48. package/dist/file/property-utils.js.map +1 -0
  49. package/package.json +2 -1
  50. package/src/core/evaluator/base.ts +71 -0
  51. package/src/core/evaluator/color.ts +37 -0
  52. package/src/core/evaluator/excluded.ts +63 -0
  53. package/src/core/evaluator/filter.ts +33 -0
  54. package/src/core/evaluator/included.ts +74 -0
  55. package/src/core/evaluator/index.ts +5 -0
  56. package/src/core/expression-utils.ts +53 -0
  57. package/src/core/frontmatter-value.ts +528 -0
  58. package/src/core/index.ts +3 -1
  59. package/src/file/index.ts +1 -0
  60. package/src/file/link-parser.ts +73 -0
  61. package/src/file/property-utils.ts +114 -0
  62. package/dist/core/evaluator-base.d.ts +0 -52
  63. package/dist/core/evaluator-base.d.ts.map +0 -1
  64. package/dist/core/evaluator-base.js +0 -84
  65. package/dist/core/evaluator-base.js.map +0 -1
  66. package/src/core/evaluator-base.ts +0 -118
@@ -0,0 +1,114 @@
1
+ import { normalizePath } from "obsidian";
2
+
3
+ import { formatWikiLink, parsePropertyLinks } from "./link-parser";
4
+
5
+ /**
6
+ * Adds a link to a property, avoiding duplicates using normalized path comparison.
7
+ * Prevents cycles and duplicate relationships by comparing normalized paths.
8
+ *
9
+ * **Important**: linkPath should be WITHOUT .md extension (wikilink format).
10
+ *
11
+ * @param currentValue - The current property value (can be string, string[], or undefined)
12
+ * @param linkPath - The file path to add (without .md extension, e.g., "folder/file")
13
+ * @returns New array with link added, or same array if link already exists
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * addLinkToProperty(undefined, "MyNote") // ["[[MyNote]]"]
18
+ * addLinkToProperty("[[Note1]]", "Note2") // ["[[Note1]]", "[[Note2]]"]
19
+ * addLinkToProperty(["[[Note1]]"], "Note2") // ["[[Note1]]", "[[Note2]]"]
20
+ * addLinkToProperty(["[[Note1]]"], "Note1") // ["[[Note1]]"] (no change - duplicate prevented)
21
+ * addLinkToProperty(["[[Folder/Note]]"], "folder/note") // ["[[Folder/Note]]", "[[folder/note|note]]"] (case-sensitive, different entry)
22
+ * ```
23
+ */
24
+ export function addLinkToProperty(
25
+ currentValue: string | string[] | undefined,
26
+ linkPath: string
27
+ ): string[] {
28
+ // Handle undefined or null
29
+ if (currentValue === undefined || currentValue === null) {
30
+ return [formatWikiLink(linkPath)];
31
+ }
32
+
33
+ // Normalize to array
34
+ const currentArray = Array.isArray(currentValue) ? currentValue : [currentValue];
35
+
36
+ const existingPaths = parsePropertyLinks(currentArray);
37
+
38
+ // Normalize paths for comparison to prevent duplicates with different casing or separators
39
+ const normalizedLinkPath = normalizePath(linkPath);
40
+
41
+ const normalizedExistingPaths = existingPaths.map((p) => normalizePath(p));
42
+
43
+ // Only add if not already present (using normalized path comparison)
44
+ if (!normalizedExistingPaths.includes(normalizedLinkPath)) {
45
+ return [...currentArray, formatWikiLink(linkPath)];
46
+ }
47
+
48
+ return currentArray;
49
+ }
50
+
51
+ /**
52
+ * Removes a link from a property using normalized path comparison.
53
+ *
54
+ * @param currentValue - The current property value (can be string, string[], or undefined)
55
+ * @param linkPath - The file path to remove (without .md extension)
56
+ * @returns New array with link removed (can be empty)
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * removeLinkFromProperty(["[[Note1]]", "[[Note2]]"], "Note1") // ["[[Note2]]"]
61
+ * removeLinkFromProperty(["[[Note1]]"], "Note1") // []
62
+ * removeLinkFromProperty("[[Note1]]", "Note1") // []
63
+ * removeLinkFromProperty(undefined, "Note1") // []
64
+ * removeLinkFromProperty(["[[Folder/Note]]"], "Folder/Note") // [] (case-sensitive removal)
65
+ * ```
66
+ */
67
+ export function removeLinkFromProperty(
68
+ currentValue: string | string[] | undefined,
69
+ linkPath: string
70
+ ): string[] {
71
+ if (currentValue === undefined || currentValue === null) {
72
+ return [];
73
+ }
74
+
75
+ // Normalize to array
76
+ const currentArray = Array.isArray(currentValue) ? currentValue : [currentValue];
77
+
78
+ const normalizedLinkPath = normalizePath(linkPath);
79
+
80
+ return currentArray.filter((item) => {
81
+ const parsed = parsePropertyLinks([item])[0];
82
+
83
+ if (!parsed) return true; // Keep invalid entries
84
+
85
+ return normalizePath(parsed) !== normalizedLinkPath;
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Checks if a link exists in a property using normalized path comparison.
91
+ *
92
+ * @param currentValue - The current property value (can be string, string[], or undefined)
93
+ * @param linkPath - The file path to check (without .md extension)
94
+ * @returns True if the link exists
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * hasLinkInProperty(["[[Note1]]", "[[Note2]]"], "Note1") // true
99
+ * hasLinkInProperty("[[Note1]]", "Note1") // true
100
+ * hasLinkInProperty([], "Note1") // false
101
+ * hasLinkInProperty(undefined, "Note1") // false
102
+ * hasLinkInProperty(["[[Folder/Note]]"], "Folder/Note") // true (case-sensitive match)
103
+ * ```
104
+ */
105
+ export function hasLinkInProperty(
106
+ currentValue: string | string[] | undefined,
107
+ linkPath: string
108
+ ): boolean {
109
+ const existingPaths = parsePropertyLinks(currentValue);
110
+
111
+ const normalizedLinkPath = normalizePath(linkPath);
112
+
113
+ return existingPaths.some((path) => normalizePath(path) === normalizedLinkPath);
114
+ }
@@ -1,52 +0,0 @@
1
- import type { BehaviorSubject } from "rxjs";
2
- export interface BaseRule {
3
- id: string;
4
- expression: string;
5
- enabled: boolean;
6
- }
7
- /**
8
- * Generic base class for evaluating JavaScript expressions against frontmatter objects.
9
- * Provides reactive compilation of rules via RxJS subscription and safe evaluation.
10
- */
11
- export declare abstract class BaseEvaluator<TRule extends BaseRule, TSettings> {
12
- protected compiledRules: Array<TRule & {
13
- fn: (frontmatter: Record<string, unknown>) => boolean;
14
- }>;
15
- private settingsSubscription;
16
- constructor(settingsStore: BehaviorSubject<TSettings>);
17
- /**
18
- * Extract rules from settings object. Must be implemented by subclasses.
19
- */
20
- protected abstract extractRules(settings: TSettings): TRule[];
21
- /**
22
- * Compile rules into executable functions with error handling.
23
- */
24
- private compileRules;
25
- /**
26
- * Evaluate a single rule against frontmatter. Returns the result or undefined if error.
27
- */
28
- protected evaluateRule(rule: TRule & {
29
- fn: (frontmatter: Record<string, unknown>) => boolean;
30
- }, frontmatter: Record<string, unknown>): boolean | undefined;
31
- /**
32
- * Convert evaluation result to boolean - only explicit true is considered truthy.
33
- */
34
- protected isTruthy(result: boolean | undefined): boolean;
35
- /**
36
- * Clean up subscriptions and compiled rules.
37
- */
38
- destroy(): void;
39
- /**
40
- * Get the number of active (compiled) rules.
41
- */
42
- getActiveRuleCount(): number;
43
- /**
44
- * Get information about all rules including their validity.
45
- */
46
- getRuleInfo(): Array<{
47
- expression: string;
48
- isValid: boolean;
49
- enabled: boolean;
50
- }>;
51
- }
52
- //# sourceMappingURL=evaluator-base.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"evaluator-base.d.ts","sourceRoot":"","sources":["../../src/core/evaluator-base.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,MAAM,CAAC;AAE1D,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,8BAAsB,aAAa,CAAC,KAAK,SAAS,QAAQ,EAAE,SAAS;IACpE,SAAS,CAAC,aAAa,EAAE,KAAK,CAC7B,KAAK,GAAG;QAAE,EAAE,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAA;KAAE,CACjE,CAAM;IACP,OAAO,CAAC,oBAAoB,CAA6B;gBAE7C,aAAa,EAAE,eAAe,CAAC,SAAS,CAAC;IAUrD;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,YAAY,CAAC,QAAQ,EAAE,SAAS,GAAG,KAAK,EAAE;IAE7D;;OAEG;IACH,OAAO,CAAC,YAAY;IA6BpB;;OAEG;IACH,SAAS,CAAC,YAAY,CACrB,IAAI,EAAE,KAAK,GAAG;QAAE,EAAE,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAA;KAAE,EACvE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,OAAO,GAAG,SAAS;IAStB;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO;IAIxD;;OAEG;IACH,OAAO,IAAI,IAAI;IAQf;;OAEG;IACH,kBAAkB,IAAI,MAAM;IAI5B;;OAEG;IACH,WAAW,IAAI,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;CAShF"}
@@ -1,84 +0,0 @@
1
- /**
2
- * Generic base class for evaluating JavaScript expressions against frontmatter objects.
3
- * Provides reactive compilation of rules via RxJS subscription and safe evaluation.
4
- */
5
- export class BaseEvaluator {
6
- constructor(settingsStore) {
7
- this.compiledRules = [];
8
- this.settingsSubscription = null;
9
- const initialRules = this.extractRules(settingsStore.value);
10
- this.compileRules(initialRules);
11
- this.settingsSubscription = settingsStore.subscribe((settings) => {
12
- const newRules = this.extractRules(settings);
13
- this.compileRules(newRules);
14
- });
15
- }
16
- /**
17
- * Compile rules into executable functions with error handling.
18
- */
19
- compileRules(rules) {
20
- this.compiledRules = [];
21
- for (const rule of rules) {
22
- if (!rule.enabled || !rule.expression.trim())
23
- continue;
24
- try {
25
- const cleanExpression = rule.expression.trim();
26
- // Create a function that takes 'fm' (frontmatter) as parameter
27
- // and evaluates the expression in that context
28
- const fn = new Function("fm", `return (${cleanExpression});`);
29
- // Test the function with a dummy object to catch syntax errors early
30
- fn({});
31
- this.compiledRules.push(Object.assign(Object.assign({}, rule), { expression: cleanExpression, fn }));
32
- }
33
- catch (error) {
34
- console.warn(`Invalid rule expression "${rule.expression}":`, error);
35
- }
36
- }
37
- }
38
- /**
39
- * Evaluate a single rule against frontmatter. Returns the result or undefined if error.
40
- */
41
- evaluateRule(rule, frontmatter) {
42
- try {
43
- return rule.fn(frontmatter);
44
- }
45
- catch (error) {
46
- console.warn(`Error evaluating rule "${rule.expression}":`, error);
47
- return undefined;
48
- }
49
- }
50
- /**
51
- * Convert evaluation result to boolean - only explicit true is considered truthy.
52
- */
53
- isTruthy(result) {
54
- return result === true;
55
- }
56
- /**
57
- * Clean up subscriptions and compiled rules.
58
- */
59
- destroy() {
60
- if (this.settingsSubscription) {
61
- this.settingsSubscription.unsubscribe();
62
- this.settingsSubscription = null;
63
- }
64
- this.compiledRules = [];
65
- }
66
- /**
67
- * Get the number of active (compiled) rules.
68
- */
69
- getActiveRuleCount() {
70
- return this.compiledRules.length;
71
- }
72
- /**
73
- * Get information about all rules including their validity.
74
- */
75
- getRuleInfo() {
76
- const validExpressions = new Set(this.compiledRules.map((r) => r.expression));
77
- return this.compiledRules.map((rule) => ({
78
- expression: rule.expression,
79
- isValid: validExpressions.has(rule.expression),
80
- enabled: rule.enabled,
81
- }));
82
- }
83
- }
84
- //# sourceMappingURL=evaluator-base.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"evaluator-base.js","sourceRoot":"","sources":["../../src/core/evaluator-base.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorSubject, Subscription } from \"rxjs\";\n\nexport interface BaseRule {\n\tid: string;\n\texpression: string;\n\tenabled: boolean;\n}\n\n/**\n * Generic base class for evaluating JavaScript expressions against frontmatter objects.\n * Provides reactive compilation of rules via RxJS subscription and safe evaluation.\n */\nexport abstract class BaseEvaluator<TRule extends BaseRule, TSettings> {\n\tprotected compiledRules: Array<\n\t\tTRule & { fn: (frontmatter: Record<string, unknown>) => boolean }\n\t> = [];\n\tprivate settingsSubscription: Subscription | null = null;\n\n\tconstructor(settingsStore: BehaviorSubject<TSettings>) {\n\t\tconst initialRules = this.extractRules(settingsStore.value);\n\t\tthis.compileRules(initialRules);\n\n\t\tthis.settingsSubscription = settingsStore.subscribe((settings) => {\n\t\t\tconst newRules = this.extractRules(settings);\n\t\t\tthis.compileRules(newRules);\n\t\t});\n\t}\n\n\t/**\n\t * Extract rules from settings object. Must be implemented by subclasses.\n\t */\n\tprotected abstract extractRules(settings: TSettings): TRule[];\n\n\t/**\n\t * Compile rules into executable functions with error handling.\n\t */\n\tprivate compileRules(rules: TRule[]): void {\n\t\tthis.compiledRules = [];\n\n\t\tfor (const rule of rules) {\n\t\t\tif (!rule.enabled || !rule.expression.trim()) continue;\n\n\t\t\ttry {\n\t\t\t\tconst cleanExpression = rule.expression.trim();\n\n\t\t\t\t// Create a function that takes 'fm' (frontmatter) as parameter\n\t\t\t\t// and evaluates the expression in that context\n\t\t\t\tconst fn = new Function(\"fm\", `return (${cleanExpression});`) as (\n\t\t\t\t\tfrontmatter: Record<string, unknown>\n\t\t\t\t) => boolean;\n\n\t\t\t\t// Test the function with a dummy object to catch syntax errors early\n\t\t\t\tfn({});\n\n\t\t\t\tthis.compiledRules.push({\n\t\t\t\t\t...rule,\n\t\t\t\t\texpression: cleanExpression,\n\t\t\t\t\tfn,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(`Invalid rule expression \"${rule.expression}\":`, error);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Evaluate a single rule against frontmatter. Returns the result or undefined if error.\n\t */\n\tprotected evaluateRule(\n\t\trule: TRule & { fn: (frontmatter: Record<string, unknown>) => boolean },\n\t\tfrontmatter: Record<string, unknown>\n\t): boolean | undefined {\n\t\ttry {\n\t\t\treturn rule.fn(frontmatter);\n\t\t} catch (error) {\n\t\t\tconsole.warn(`Error evaluating rule \"${rule.expression}\":`, error);\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Convert evaluation result to boolean - only explicit true is considered truthy.\n\t */\n\tprotected isTruthy(result: boolean | undefined): boolean {\n\t\treturn result === true;\n\t}\n\n\t/**\n\t * Clean up subscriptions and compiled rules.\n\t */\n\tdestroy(): void {\n\t\tif (this.settingsSubscription) {\n\t\t\tthis.settingsSubscription.unsubscribe();\n\t\t\tthis.settingsSubscription = null;\n\t\t}\n\t\tthis.compiledRules = [];\n\t}\n\n\t/**\n\t * Get the number of active (compiled) rules.\n\t */\n\tgetActiveRuleCount(): number {\n\t\treturn this.compiledRules.length;\n\t}\n\n\t/**\n\t * Get information about all rules including their validity.\n\t */\n\tgetRuleInfo(): Array<{ expression: string; isValid: boolean; enabled: boolean }> {\n\t\tconst validExpressions = new Set(this.compiledRules.map((r) => r.expression));\n\n\t\treturn this.compiledRules.map((rule) => ({\n\t\t\texpression: rule.expression,\n\t\t\tisValid: validExpressions.has(rule.expression),\n\t\t\tenabled: rule.enabled,\n\t\t}));\n\t}\n}\n"]}
@@ -1,118 +0,0 @@
1
- import type { BehaviorSubject, Subscription } from "rxjs";
2
-
3
- export interface BaseRule {
4
- id: string;
5
- expression: string;
6
- enabled: boolean;
7
- }
8
-
9
- /**
10
- * Generic base class for evaluating JavaScript expressions against frontmatter objects.
11
- * Provides reactive compilation of rules via RxJS subscription and safe evaluation.
12
- */
13
- export abstract class BaseEvaluator<TRule extends BaseRule, TSettings> {
14
- protected compiledRules: Array<
15
- TRule & { fn: (frontmatter: Record<string, unknown>) => boolean }
16
- > = [];
17
- private settingsSubscription: Subscription | null = null;
18
-
19
- constructor(settingsStore: BehaviorSubject<TSettings>) {
20
- const initialRules = this.extractRules(settingsStore.value);
21
- this.compileRules(initialRules);
22
-
23
- this.settingsSubscription = settingsStore.subscribe((settings) => {
24
- const newRules = this.extractRules(settings);
25
- this.compileRules(newRules);
26
- });
27
- }
28
-
29
- /**
30
- * Extract rules from settings object. Must be implemented by subclasses.
31
- */
32
- protected abstract extractRules(settings: TSettings): TRule[];
33
-
34
- /**
35
- * Compile rules into executable functions with error handling.
36
- */
37
- private compileRules(rules: TRule[]): void {
38
- this.compiledRules = [];
39
-
40
- for (const rule of rules) {
41
- if (!rule.enabled || !rule.expression.trim()) continue;
42
-
43
- try {
44
- const cleanExpression = rule.expression.trim();
45
-
46
- // Create a function that takes 'fm' (frontmatter) as parameter
47
- // and evaluates the expression in that context
48
- const fn = new Function("fm", `return (${cleanExpression});`) as (
49
- frontmatter: Record<string, unknown>
50
- ) => boolean;
51
-
52
- // Test the function with a dummy object to catch syntax errors early
53
- fn({});
54
-
55
- this.compiledRules.push({
56
- ...rule,
57
- expression: cleanExpression,
58
- fn,
59
- });
60
- } catch (error) {
61
- console.warn(`Invalid rule expression "${rule.expression}":`, error);
62
- }
63
- }
64
- }
65
-
66
- /**
67
- * Evaluate a single rule against frontmatter. Returns the result or undefined if error.
68
- */
69
- protected evaluateRule(
70
- rule: TRule & { fn: (frontmatter: Record<string, unknown>) => boolean },
71
- frontmatter: Record<string, unknown>
72
- ): boolean | undefined {
73
- try {
74
- return rule.fn(frontmatter);
75
- } catch (error) {
76
- console.warn(`Error evaluating rule "${rule.expression}":`, error);
77
- return undefined;
78
- }
79
- }
80
-
81
- /**
82
- * Convert evaluation result to boolean - only explicit true is considered truthy.
83
- */
84
- protected isTruthy(result: boolean | undefined): boolean {
85
- return result === true;
86
- }
87
-
88
- /**
89
- * Clean up subscriptions and compiled rules.
90
- */
91
- destroy(): void {
92
- if (this.settingsSubscription) {
93
- this.settingsSubscription.unsubscribe();
94
- this.settingsSubscription = null;
95
- }
96
- this.compiledRules = [];
97
- }
98
-
99
- /**
100
- * Get the number of active (compiled) rules.
101
- */
102
- getActiveRuleCount(): number {
103
- return this.compiledRules.length;
104
- }
105
-
106
- /**
107
- * Get information about all rules including their validity.
108
- */
109
- getRuleInfo(): Array<{ expression: string; isValid: boolean; enabled: boolean }> {
110
- const validExpressions = new Set(this.compiledRules.map((r) => r.expression));
111
-
112
- return this.compiledRules.map((rule) => ({
113
- expression: rule.expression,
114
- isValid: validExpressions.has(rule.expression),
115
- enabled: rule.enabled,
116
- }));
117
- }
118
- }