@real1ty-obsidian-plugins/utils 2.16.0 → 2.19.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.
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/core/evaluator/base.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,MAAM,CAAC;AAI1D,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,KAAK,EAAE,KAAK,EAAE,CAAM;IAC9B,OAAO,CAAC,iBAAiB,CAA2D;IACpF,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,YAAY,CAA6B;gBAErC,aAAa,EAAE,eAAe,CAAC,SAAS,CAAC;IAQrD,SAAS,CAAC,QAAQ,CAAC,YAAY,CAAC,QAAQ,EAAE,SAAS,GAAG,KAAK,EAAE;IAE7D,OAAO,IAAI,IAAI;IAMf,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IA2ClF,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO;CAGvC"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/core/evaluator/base.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,MAAM,CAAC;AAI1D,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,KAAK,EAAE,KAAK,EAAE,CAAM;IAC9B,OAAO,CAAC,iBAAiB,CAA2D;IACpF,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,YAAY,CAA6B;gBAErC,aAAa,EAAE,eAAe,CAAC,SAAS,CAAC;IAQrD,SAAS,CAAC,QAAQ,CAAC,YAAY,CAAC,QAAQ,EAAE,SAAS,GAAG,KAAK,EAAE;IAE7D,OAAO,IAAI,IAAI;IAMf,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAiDlF,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO;CAGvC"}
@@ -50,6 +50,12 @@ export class BaseEvaluator {
50
50
  return result === true;
51
51
  }
52
52
  catch (error) {
53
+ // Suppress ReferenceError logs - these occur when properties don't exist in frontmatter
54
+ // which is expected behavior (e.g., Status === 'Done' when Status is undefined)
55
+ if (error instanceof ReferenceError) {
56
+ return false;
57
+ }
58
+ // Log other errors (syntax errors, etc.) as they indicate actual problems
53
59
  console.warn(`Invalid expression (${rule.id}):`, rule.expression, error);
54
60
  return false;
55
61
  }
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/core/evaluator/base.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAQ/E;;;GAGG;AACH,MAAM,OAAgB,aAAa;IAMlC,YAAY,aAAyC;QAL3C,UAAK,GAAY,EAAE,CAAC;QACtB,sBAAiB,GAAG,IAAI,GAAG,EAAgD,CAAC;QAC5E,oBAAe,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC5C,iBAAY,GAAwB,IAAI,CAAC;QAGhD,IAAI,CAAC,YAAY,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE;YACxD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;YACzC,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAC/B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC9B,CAAC,CAAC,CAAC;IACJ,CAAC;IAID,OAAO;;QACN,MAAA,IAAI,CAAC,YAAY,0CAAE,WAAW,EAAE,CAAC;QACjC,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;IAC9B,CAAC;IAES,YAAY,CAAC,IAAW,EAAE,WAAoC;QACvE,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACvC,OAAO,KAAK,CAAC;QACd,CAAC;QAED,IAAI,CAAC;YACJ,sEAAsE;YACtE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;YACtD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1D,MAAM,OAAO,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;YAEzE,qFAAqF;YACrF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACxB,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,YAAY,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC;gBAC3D,IAAI,CAAC,eAAe,GAAG,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;gBACjE,0DAA0D;gBAC1D,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC;YAChC,CAAC;YAED,IAAI,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAEvD,IAAI,CAAC,YAAY,EAAE,CAAC;gBACnB,MAAM,SAAS,GAAG,kBAAkB,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;gBAC5E,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC;gBACzD,YAAY,GAAG,IAAI,QAAQ,CAAC,GAAG,MAAM,EAAE,wBAAwB,SAAS,GAAG,CAE/D,CAAC;gBACb,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;YACnD,CAAC;YAED,uFAAuF;YACvF,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CACzD,CAAC,GAAG,EAAE,EAAE,WAAC,OAAA,MAAA,WAAW,CAAC,GAAG,CAAC,mCAAI,SAAS,CAAA,EAAA,CACtC,CAAC;YACF,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,MAAM,CAAC,CAAC;YAEvC,OAAO,MAAM,KAAK,IAAI,CAAC;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,uBAAuB,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;YACzE,OAAO,KAAK,CAAC;QACd,CAAC;IACF,CAAC;IAES,QAAQ,CAAC,KAAU;QAC5B,OAAO,KAAK,KAAK,IAAI,CAAC;IACvB,CAAC;CACD","sourcesContent":["import type { BehaviorSubject, Subscription } from \"rxjs\";\n\nimport { buildPropertyMapping, sanitizeExpression } from \"../expression-utils\";\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 rules: TRule[] = [];\n\tprivate compiledFunctions = new Map<string, ((...args: any[]) => boolean) | null>();\n\tprivate propertyMapping = new Map<string, string>();\n\tprivate subscription: Subscription | null = null;\n\n\tconstructor(settingsStore: BehaviorSubject<TSettings>) {\n\t\tthis.subscription = settingsStore.subscribe((settings) => {\n\t\t\tthis.rules = this.extractRules(settings);\n\t\t\tthis.compiledFunctions.clear();\n\t\t\tthis.propertyMapping.clear();\n\t\t});\n\t}\n\n\tprotected abstract extractRules(settings: TSettings): TRule[];\n\n\tdestroy(): void {\n\t\tthis.subscription?.unsubscribe();\n\t\tthis.compiledFunctions.clear();\n\t\tthis.propertyMapping.clear();\n\t}\n\n\tprotected evaluateRule(rule: TRule, frontmatter: Record<string, unknown>): boolean {\n\t\tif (!rule.enabled || !rule.expression) {\n\t\t\treturn false;\n\t\t}\n\n\t\ttry {\n\t\t\t// Progressively build property mapping as we encounter new properties\n\t\t\tconst currentKeys = new Set(Object.keys(frontmatter));\n\t\t\tconst existingKeys = new Set(this.propertyMapping.keys());\n\t\t\tconst newKeys = [...currentKeys].filter((key) => !existingKeys.has(key));\n\n\t\t\t// If new properties are found, rebuild the mapping and invalidate compiled functions\n\t\t\tif (newKeys.length > 0) {\n\t\t\t\tconst allKeys = new Set([...existingKeys, ...currentKeys]);\n\t\t\t\tthis.propertyMapping = buildPropertyMapping(Array.from(allKeys));\n\t\t\t\t// Clear compiled functions since property mapping changed\n\t\t\t\tthis.compiledFunctions.clear();\n\t\t\t}\n\n\t\t\tlet compiledFunc = this.compiledFunctions.get(rule.id);\n\n\t\t\tif (!compiledFunc) {\n\t\t\t\tconst sanitized = sanitizeExpression(rule.expression, this.propertyMapping);\n\t\t\t\tconst params = Array.from(this.propertyMapping.values());\n\t\t\t\tcompiledFunc = new Function(...params, `\"use strict\"; return ${sanitized};`) as (\n\t\t\t\t\t...args: any[]\n\t\t\t\t) => boolean;\n\t\t\t\tthis.compiledFunctions.set(rule.id, compiledFunc);\n\t\t\t}\n\n\t\t\t// Use undefined for missing properties instead of letting them be undefined implicitly\n\t\t\tconst values = Array.from(this.propertyMapping.keys()).map(\n\t\t\t\t(key) => frontmatter[key] ?? undefined\n\t\t\t);\n\t\t\tconst result = compiledFunc(...values);\n\n\t\t\treturn result === true;\n\t\t} catch (error) {\n\t\t\tconsole.warn(`Invalid expression (${rule.id}):`, rule.expression, error);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprotected isTruthy(value: any): boolean {\n\t\treturn value === true;\n\t}\n}\n"]}
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/core/evaluator/base.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorSubject, Subscription } from \"rxjs\";\n\nimport { buildPropertyMapping, sanitizeExpression } from \"../expression-utils\";\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 rules: TRule[] = [];\n\tprivate compiledFunctions = new Map<string, ((...args: any[]) => boolean) | null>();\n\tprivate propertyMapping = new Map<string, string>();\n\tprivate subscription: Subscription | null = null;\n\n\tconstructor(settingsStore: BehaviorSubject<TSettings>) {\n\t\tthis.subscription = settingsStore.subscribe((settings) => {\n\t\t\tthis.rules = this.extractRules(settings);\n\t\t\tthis.compiledFunctions.clear();\n\t\t\tthis.propertyMapping.clear();\n\t\t});\n\t}\n\n\tprotected abstract extractRules(settings: TSettings): TRule[];\n\n\tdestroy(): void {\n\t\tthis.subscription?.unsubscribe();\n\t\tthis.compiledFunctions.clear();\n\t\tthis.propertyMapping.clear();\n\t}\n\n\tprotected evaluateRule(rule: TRule, frontmatter: Record<string, unknown>): boolean {\n\t\tif (!rule.enabled || !rule.expression) {\n\t\t\treturn false;\n\t\t}\n\n\t\ttry {\n\t\t\t// Progressively build property mapping as we encounter new properties\n\t\t\tconst currentKeys = new Set(Object.keys(frontmatter));\n\t\t\tconst existingKeys = new Set(this.propertyMapping.keys());\n\t\t\tconst newKeys = [...currentKeys].filter((key) => !existingKeys.has(key));\n\n\t\t\t// If new properties are found, rebuild the mapping and invalidate compiled functions\n\t\t\tif (newKeys.length > 0) {\n\t\t\t\tconst allKeys = new Set([...existingKeys, ...currentKeys]);\n\t\t\t\tthis.propertyMapping = buildPropertyMapping(Array.from(allKeys));\n\t\t\t\t// Clear compiled functions since property mapping changed\n\t\t\t\tthis.compiledFunctions.clear();\n\t\t\t}\n\n\t\t\tlet compiledFunc = this.compiledFunctions.get(rule.id);\n\n\t\t\tif (!compiledFunc) {\n\t\t\t\tconst sanitized = sanitizeExpression(rule.expression, this.propertyMapping);\n\t\t\t\tconst params = Array.from(this.propertyMapping.values());\n\t\t\t\tcompiledFunc = new Function(...params, `\"use strict\"; return ${sanitized};`) as (\n\t\t\t\t\t...args: any[]\n\t\t\t\t) => boolean;\n\t\t\t\tthis.compiledFunctions.set(rule.id, compiledFunc);\n\t\t\t}\n\n\t\t\t// Use undefined for missing properties instead of letting them be undefined implicitly\n\t\t\tconst values = Array.from(this.propertyMapping.keys()).map(\n\t\t\t\t(key) => frontmatter[key] ?? undefined\n\t\t\t);\n\t\t\tconst result = compiledFunc(...values);\n\n\t\t\treturn result === true;\n\t\t} catch (error) {\n\t\t\t// Suppress ReferenceError logs - these occur when properties don't exist in frontmatter\n\t\t\t// which is expected behavior (e.g., Status === 'Done' when Status is undefined)\n\t\t\tif (error instanceof ReferenceError) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t// Log other errors (syntax errors, etc.) as they indicate actual problems\n\t\t\tconsole.warn(`Invalid expression (${rule.id}):`, rule.expression, error);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprotected isTruthy(value: any): boolean {\n\t\treturn value === true;\n\t}\n}\n"]}
@@ -10,10 +10,11 @@ export type IndexerFrontmatter = Record<string, unknown>;
10
10
  */
11
11
  export interface IndexerConfig {
12
12
  /**
13
- * Directory to scan for files (e.g., "Calendar", "Notes")
14
- * If empty string or undefined, scans entire vault
13
+ * Function that determines whether a file should be included in the indexer.
14
+ * Returns true if the file should be indexed, false otherwise.
15
+ * If not provided, all files are included.
15
16
  */
16
- directory?: string;
17
+ includeFile?: (path: string) => boolean;
17
18
  /**
18
19
  * Properties to exclude when comparing frontmatter diffs
19
20
  */
@@ -69,18 +70,10 @@ export declare class Indexer {
69
70
  private frontmatterCache;
70
71
  readonly events$: Observable<IndexerEvent>;
71
72
  readonly indexingComplete$: Observable<boolean>;
72
- constructor(_app: App, configStore: BehaviorSubject<IndexerConfig>);
73
- /**
74
- * Start the indexer and perform initial scan
75
- */
73
+ constructor(app: App, configStore: BehaviorSubject<IndexerConfig>);
74
+ private normalizeConfig;
76
75
  start(): Promise<void>;
77
- /**
78
- * Stop the indexer and clean up subscriptions
79
- */
80
76
  stop(): void;
81
- /**
82
- * Clear cache and rescan all files
83
- */
84
77
  resync(): void;
85
78
  /**
86
79
  * Scan all markdown files in the configured directory
@@ -90,9 +83,6 @@ export declare class Indexer {
90
83
  * Create an observable from a vault event
91
84
  */
92
85
  private fromVaultEvent;
93
- /**
94
- * Type guard to check if file is a markdown file
95
- */
96
86
  private static isMarkdownFile;
97
87
  /**
98
88
  * Filter to only relevant markdown files in configured directory
@@ -110,9 +100,5 @@ export declare class Indexer {
110
100
  * Build an indexer event from a file
111
101
  */
112
102
  private buildEvent;
113
- /**
114
- * Check if file is in the configured directory
115
- */
116
- private isRelevantFile;
117
103
  }
118
104
  //# sourceMappingURL=indexer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../../src/core/indexer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAA6D,MAAM,UAAU,CAAC;AAC/F,OAAO,EACN,KAAK,eAAe,EAKpB,KAAK,UAAU,EAKf,MAAM,MAAM,CAAC;AAGd,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEpF;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAEhC;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,kBAAkB,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,cAAc,CAAC;AAE/D;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACpC,eAAe,CAAC,EAAE,eAAe,CAAC;CAClC;AAQD;;;;;;GAMG;AACH,qBAAa,OAAO;IACnB,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,uBAAuB,CAAyC;IACxE,OAAO,CAAC,gBAAgB,CAA8C;IAEtE,SAAgB,OAAO,EAAE,UAAU,CAAC,YAAY,CAAC,CAAC;IAClD,SAAgB,iBAAiB,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;gBAE3C,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,eAAe,CAAC,aAAa,CAAC;IAkClE;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B;;OAEG;IACH,IAAI,IAAI,IAAI;IAQZ;;OAEG;IACH,MAAM,IAAI,IAAI;IAMd;;OAEG;YACW,YAAY;IA+B1B;;OAEG;IACH,OAAO,CAAC,cAAc;IA6BtB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IAI7B;;OAEG;IACH,OAAO,CAAC,eAAe;IAQvB;;OAEG;IACH,OAAO,CAAC,cAAc;IAQtB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA4C9B;;OAEG;YACW,UAAU;IA+BxB;;OAEG;IACH,OAAO,CAAC,cAAc;CAGtB"}
1
+ {"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../../src/core/indexer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAA6D,MAAM,UAAU,CAAC;AAC/F,OAAO,EACN,KAAK,eAAe,EAKpB,KAAK,UAAU,EAKf,MAAM,MAAM,CAAC;AAEd,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEpF;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAExC;;OAEG;IACH,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAEhC;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,kBAAkB,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,cAAc,CAAC;AAE/D;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACpC,eAAe,CAAC,EAAE,eAAe,CAAC;CAClC;AAQD;;;;;;GAMG;AACH,qBAAa,OAAO;IACnB,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,uBAAuB,CAAyC;IACxE,OAAO,CAAC,gBAAgB,CAA8C;IAEtE,SAAgB,OAAO,EAAE,UAAU,CAAC,YAAY,CAAC,CAAC;IAClD,SAAgB,iBAAiB,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;gBAE3C,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,eAAe,CAAC,aAAa,CAAC;IAoBjE,OAAO,CAAC,eAAe;IASjB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,IAAI,IAAI,IAAI;IAQZ,MAAM,IAAI,IAAI;IAMd;;OAEG;YACW,YAAY;IA+B1B;;OAEG;IACH,OAAO,CAAC,cAAc;IA6BtB,OAAO,CAAC,MAAM,CAAC,cAAc;IAI7B;;OAEG;IACH,OAAO,CAAC,eAAe;IAQvB;;OAEG;IACH,OAAO,CAAC,cAAc;IAQtB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA4C9B;;OAEG;YACW,UAAU;CA8BxB"}
@@ -2,7 +2,6 @@ import { __awaiter } from "tslib";
2
2
  import { TFile } from "obsidian";
3
3
  import { from, fromEventPattern, lastValueFrom, merge, of, BehaviorSubject as RxBehaviorSubject, Subject, } from "rxjs";
4
4
  import { debounceTime, filter, groupBy, map, mergeMap, switchMap, toArray } from "rxjs/operators";
5
- import { isFileInConfiguredDirectory } from "../file/file";
6
5
  import { compareFrontmatter } from "../file/frontmatter-diff";
7
6
  /**
8
7
  * Generic indexer that listens to Obsidian vault events and emits
@@ -12,32 +11,19 @@ import { compareFrontmatter } from "../file/frontmatter-diff";
12
11
  * that needs to track file changes with frontmatter.
13
12
  */
14
13
  export class Indexer {
15
- constructor(_app, configStore) {
14
+ constructor(app, configStore) {
16
15
  this.fileSub = null;
17
16
  this.configSubscription = null;
18
17
  this.scanEventsSubject = new Subject();
19
18
  this.indexingCompleteSubject = new RxBehaviorSubject(false);
20
19
  this.frontmatterCache = new Map();
21
- this.vault = _app.vault;
22
- this.metadataCache = _app.metadataCache;
23
- // Set defaults
24
- this.config = {
25
- directory: configStore.value.directory || "",
26
- excludedDiffProps: configStore.value.excludedDiffProps || new Set(),
27
- scanConcurrency: configStore.value.scanConcurrency || 10,
28
- debounceMs: configStore.value.debounceMs || 100,
29
- };
30
- // Subscribe to config changes
20
+ this.vault = app.vault;
21
+ this.metadataCache = app.metadataCache;
22
+ this.config = this.normalizeConfig(configStore.value);
31
23
  this.configSubscription = configStore.subscribe((newConfig) => {
32
- const directoryChanged = this.config.directory !== (newConfig.directory || "");
33
- this.config = {
34
- directory: newConfig.directory || "",
35
- excludedDiffProps: newConfig.excludedDiffProps || new Set(),
36
- scanConcurrency: newConfig.scanConcurrency || 10,
37
- debounceMs: newConfig.debounceMs || 100,
38
- };
39
- // Rescan if directory changed
40
- if (directoryChanged) {
24
+ const includeFileChanged = this.config.includeFile !== this.normalizeConfig(newConfig).includeFile;
25
+ this.config = this.normalizeConfig(newConfig);
26
+ if (includeFileChanged) {
41
27
  this.indexingCompleteSubject.next(false);
42
28
  void this.scanAllFiles();
43
29
  }
@@ -45,9 +31,14 @@ export class Indexer {
45
31
  this.events$ = this.scanEventsSubject.asObservable();
46
32
  this.indexingComplete$ = this.indexingCompleteSubject.asObservable();
47
33
  }
48
- /**
49
- * Start the indexer and perform initial scan
50
- */
34
+ normalizeConfig(config) {
35
+ return {
36
+ includeFile: config.includeFile || (() => true),
37
+ excludedDiffProps: config.excludedDiffProps || new Set(),
38
+ scanConcurrency: config.scanConcurrency || 10,
39
+ debounceMs: config.debounceMs || 100,
40
+ };
41
+ }
51
42
  start() {
52
43
  return __awaiter(this, void 0, void 0, function* () {
53
44
  this.indexingCompleteSubject.next(false);
@@ -58,9 +49,6 @@ export class Indexer {
58
49
  yield this.scanAllFiles();
59
50
  });
60
51
  }
61
- /**
62
- * Stop the indexer and clean up subscriptions
63
- */
64
52
  stop() {
65
53
  var _a, _b;
66
54
  (_a = this.fileSub) === null || _a === void 0 ? void 0 : _a.unsubscribe();
@@ -69,9 +57,6 @@ export class Indexer {
69
57
  this.configSubscription = null;
70
58
  this.indexingCompleteSubject.complete();
71
59
  }
72
- /**
73
- * Clear cache and rescan all files
74
- */
75
60
  resync() {
76
61
  this.frontmatterCache.clear();
77
62
  this.indexingCompleteSubject.next(false);
@@ -83,7 +68,7 @@ export class Indexer {
83
68
  scanAllFiles() {
84
69
  return __awaiter(this, void 0, void 0, function* () {
85
70
  const allFiles = this.vault.getMarkdownFiles();
86
- const relevantFiles = allFiles.filter((file) => this.isRelevantFile(file));
71
+ const relevantFiles = allFiles.filter((file) => this.config.includeFile(file.path));
87
72
  const events$ = from(relevantFiles).pipe(mergeMap((file) => __awaiter(this, void 0, void 0, function* () {
88
73
  try {
89
74
  return yield this.buildEvent(file);
@@ -122,9 +107,6 @@ export class Indexer {
122
107
  // eventName === "rename"
123
108
  return fromEventPattern((handler) => this.vault.on("rename", handler), (handler) => this.vault.off("rename", handler)).pipe(map(([file]) => file));
124
109
  }
125
- /**
126
- * Type guard to check if file is a markdown file
127
- */
128
110
  static isMarkdownFile(f) {
129
111
  return f instanceof TFile && f.extension === "md";
130
112
  }
@@ -132,7 +114,7 @@ export class Indexer {
132
114
  * Filter to only relevant markdown files in configured directory
133
115
  */
134
116
  toRelevantFiles() {
135
- return (source) => source.pipe(filter((f) => Indexer.isMarkdownFile(f)), filter((f) => this.isRelevantFile(f)));
117
+ return (source) => source.pipe(filter((f) => Indexer.isMarkdownFile(f)), filter((f) => this.config.includeFile(f.path)));
136
118
  }
137
119
  /**
138
120
  * Debounce events by file path
@@ -150,7 +132,7 @@ export class Indexer {
150
132
  const renamed$ = fromEventPattern((handler) => this.vault.on("rename", handler), (handler) => this.vault.off("rename", handler));
151
133
  const changedIntents$ = merge(created$, modified$).pipe(this.debounceByPath(this.config.debounceMs, (f) => f.path), map((file) => ({ kind: "changed", file, path: file.path })));
152
134
  const deletedIntents$ = deleted$.pipe(map((file) => ({ kind: "deleted", path: file.path })));
153
- const renamedIntents$ = renamed$.pipe(map(([f, oldPath]) => [f, oldPath]), filter(([f]) => Indexer.isMarkdownFile(f) && this.isRelevantFile(f)), mergeMap(([f, oldPath]) => [
135
+ const renamedIntents$ = renamed$.pipe(map(([f, oldPath]) => [f, oldPath]), filter(([f]) => Indexer.isMarkdownFile(f) && this.config.includeFile(f.path)), mergeMap(([f, oldPath]) => [
154
136
  { kind: "deleted", path: oldPath },
155
137
  { kind: "changed", file: f, path: f.path, oldPath },
156
138
  ]));
@@ -195,11 +177,5 @@ export class Indexer {
195
177
  return event;
196
178
  });
197
179
  }
198
- /**
199
- * Check if file is in the configured directory
200
- */
201
- isRelevantFile(file) {
202
- return isFileInConfiguredDirectory(file.path, this.config.directory);
203
- }
204
180
  }
205
181
  //# sourceMappingURL=indexer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"indexer.js","sourceRoot":"","sources":["../../src/core/indexer.ts"],"names":[],"mappings":"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","sourcesContent":["import { type App, type MetadataCache, type TAbstractFile, TFile, type Vault } from \"obsidian\";\nimport {\n\ttype BehaviorSubject,\n\tfrom,\n\tfromEventPattern,\n\tlastValueFrom,\n\tmerge,\n\ttype Observable,\n\tof,\n\tBehaviorSubject as RxBehaviorSubject,\n\tSubject,\n\ttype Subscription,\n} from \"rxjs\";\nimport { debounceTime, filter, groupBy, map, mergeMap, switchMap, toArray } from \"rxjs/operators\";\nimport { isFileInConfiguredDirectory } from \"../file/file\";\nimport { compareFrontmatter, type FrontmatterDiff } from \"../file/frontmatter-diff\";\n\n/**\n * Generic frontmatter object type for indexer\n */\nexport type IndexerFrontmatter = Record<string, unknown>;\n\n/**\n * Configuration for the generic indexer\n */\nexport interface IndexerConfig {\n\t/**\n\t * Directory to scan for files (e.g., \"Calendar\", \"Notes\")\n\t * If empty string or undefined, scans entire vault\n\t */\n\tdirectory?: string;\n\n\t/**\n\t * Properties to exclude when comparing frontmatter diffs\n\t */\n\texcludedDiffProps?: Set<string>;\n\n\t/**\n\t * Concurrency limit for file scanning operations\n\t */\n\tscanConcurrency?: number;\n\n\t/**\n\t * Debounce time in milliseconds for file change events\n\t */\n\tdebounceMs?: number;\n}\n\n/**\n * Raw file source with frontmatter and metadata\n */\nexport interface FileSource {\n\tfilePath: string;\n\tmtime: number;\n\tfrontmatter: IndexerFrontmatter;\n\tfolder: string;\n}\n\n/**\n * Types of indexer events\n */\nexport type IndexerEventType = \"file-changed\" | \"file-deleted\";\n\n/**\n * Generic indexer event\n */\nexport interface IndexerEvent {\n\ttype: IndexerEventType;\n\tfilePath: string;\n\toldPath?: string;\n\tsource?: FileSource;\n\toldFrontmatter?: IndexerFrontmatter;\n\tfrontmatterDiff?: FrontmatterDiff;\n}\n\ntype VaultEvent = \"create\" | \"modify\" | \"delete\" | \"rename\";\n\ntype FileIntent =\n\t| { kind: \"changed\"; file: TFile; path: string; oldPath?: string }\n\t| { kind: \"deleted\"; path: string };\n\n/**\n * Generic indexer that listens to Obsidian vault events and emits\n * RxJS observables with frontmatter diffs and metadata.\n *\n * This indexer is framework-agnostic and can be used by any plugin\n * that needs to track file changes with frontmatter.\n */\nexport class Indexer {\n\tprivate config: Required<IndexerConfig>;\n\tprivate fileSub: Subscription | null = null;\n\tprivate configSubscription: Subscription | null = null;\n\tprivate vault: Vault;\n\tprivate metadataCache: MetadataCache;\n\tprivate scanEventsSubject = new Subject<IndexerEvent>();\n\tprivate indexingCompleteSubject = new RxBehaviorSubject<boolean>(false);\n\tprivate frontmatterCache: Map<string, IndexerFrontmatter> = new Map();\n\n\tpublic readonly events$: Observable<IndexerEvent>;\n\tpublic readonly indexingComplete$: Observable<boolean>;\n\n\tconstructor(_app: App, configStore: BehaviorSubject<IndexerConfig>) {\n\t\tthis.vault = _app.vault;\n\t\tthis.metadataCache = _app.metadataCache;\n\n\t\t// Set defaults\n\t\tthis.config = {\n\t\t\tdirectory: configStore.value.directory || \"\",\n\t\t\texcludedDiffProps: configStore.value.excludedDiffProps || new Set(),\n\t\t\tscanConcurrency: configStore.value.scanConcurrency || 10,\n\t\t\tdebounceMs: configStore.value.debounceMs || 100,\n\t\t};\n\n\t\t// Subscribe to config changes\n\t\tthis.configSubscription = configStore.subscribe((newConfig) => {\n\t\t\tconst directoryChanged = this.config.directory !== (newConfig.directory || \"\");\n\n\t\t\tthis.config = {\n\t\t\t\tdirectory: newConfig.directory || \"\",\n\t\t\t\texcludedDiffProps: newConfig.excludedDiffProps || new Set(),\n\t\t\t\tscanConcurrency: newConfig.scanConcurrency || 10,\n\t\t\t\tdebounceMs: newConfig.debounceMs || 100,\n\t\t\t};\n\n\t\t\t// Rescan if directory changed\n\t\t\tif (directoryChanged) {\n\t\t\t\tthis.indexingCompleteSubject.next(false);\n\t\t\t\tvoid this.scanAllFiles();\n\t\t\t}\n\t\t});\n\n\t\tthis.events$ = this.scanEventsSubject.asObservable();\n\t\tthis.indexingComplete$ = this.indexingCompleteSubject.asObservable();\n\t}\n\n\t/**\n\t * Start the indexer and perform initial scan\n\t */\n\tasync start(): Promise<void> {\n\t\tthis.indexingCompleteSubject.next(false);\n\n\t\tconst fileSystemEvents$ = this.buildFileSystemEvents$();\n\n\t\tthis.fileSub = fileSystemEvents$.subscribe((event) => {\n\t\t\tthis.scanEventsSubject.next(event);\n\t\t});\n\n\t\tawait this.scanAllFiles();\n\t}\n\n\t/**\n\t * Stop the indexer and clean up subscriptions\n\t */\n\tstop(): void {\n\t\tthis.fileSub?.unsubscribe();\n\t\tthis.fileSub = null;\n\t\tthis.configSubscription?.unsubscribe();\n\t\tthis.configSubscription = null;\n\t\tthis.indexingCompleteSubject.complete();\n\t}\n\n\t/**\n\t * Clear cache and rescan all files\n\t */\n\tresync(): void {\n\t\tthis.frontmatterCache.clear();\n\t\tthis.indexingCompleteSubject.next(false);\n\t\tvoid this.scanAllFiles();\n\t}\n\n\t/**\n\t * Scan all markdown files in the configured directory\n\t */\n\tprivate async scanAllFiles(): Promise<void> {\n\t\tconst allFiles = this.vault.getMarkdownFiles();\n\t\tconst relevantFiles = allFiles.filter((file) => this.isRelevantFile(file));\n\n\t\tconst events$ = from(relevantFiles).pipe(\n\t\t\tmergeMap(async (file) => {\n\t\t\t\ttry {\n\t\t\t\t\treturn await this.buildEvent(file);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(`Error processing file ${file.path}:`, error);\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t}, this.config.scanConcurrency),\n\t\t\tfilter((event): event is IndexerEvent => event !== null),\n\t\t\ttoArray()\n\t\t);\n\n\t\ttry {\n\t\t\tconst allEvents = await lastValueFrom(events$);\n\n\t\t\tfor (const event of allEvents) {\n\t\t\t\tthis.scanEventsSubject.next(event);\n\t\t\t}\n\n\t\t\tthis.indexingCompleteSubject.next(true);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"❌ Error during file scanning:\", error);\n\t\t\tthis.indexingCompleteSubject.next(true);\n\t\t}\n\t}\n\n\t/**\n\t * Create an observable from a vault event\n\t */\n\tprivate fromVaultEvent(eventName: VaultEvent): Observable<TAbstractFile> {\n\t\tif (eventName === \"create\") {\n\t\t\treturn fromEventPattern<TAbstractFile>(\n\t\t\t\t(handler) => this.vault.on(\"create\", handler),\n\t\t\t\t(handler) => this.vault.off(\"create\", handler)\n\t\t\t);\n\t\t}\n\n\t\tif (eventName === \"modify\") {\n\t\t\treturn fromEventPattern<TAbstractFile>(\n\t\t\t\t(handler) => this.vault.on(\"modify\", handler),\n\t\t\t\t(handler) => this.vault.off(\"modify\", handler)\n\t\t\t);\n\t\t}\n\n\t\tif (eventName === \"delete\") {\n\t\t\treturn fromEventPattern<TAbstractFile>(\n\t\t\t\t(handler) => this.vault.on(\"delete\", handler),\n\t\t\t\t(handler) => this.vault.off(\"delete\", handler)\n\t\t\t);\n\t\t}\n\n\t\t// eventName === \"rename\"\n\t\treturn fromEventPattern<[TAbstractFile, string]>(\n\t\t\t(handler) => this.vault.on(\"rename\", handler),\n\t\t\t(handler) => this.vault.off(\"rename\", handler)\n\t\t).pipe(map(([file]) => file));\n\t}\n\n\t/**\n\t * Type guard to check if file is a markdown file\n\t */\n\tprivate static isMarkdownFile(f: TAbstractFile): f is TFile {\n\t\treturn f instanceof TFile && f.extension === \"md\";\n\t}\n\n\t/**\n\t * Filter to only relevant markdown files in configured directory\n\t */\n\tprivate toRelevantFiles<T extends TAbstractFile>() {\n\t\treturn (source: Observable<T>) =>\n\t\t\tsource.pipe(\n\t\t\t\tfilter((f: TAbstractFile): f is TFile => Indexer.isMarkdownFile(f)),\n\t\t\t\tfilter((f) => this.isRelevantFile(f))\n\t\t\t);\n\t}\n\n\t/**\n\t * Debounce events by file path\n\t */\n\tprivate debounceByPath<T>(ms: number, key: (x: T) => string) {\n\t\treturn (source: Observable<T>) =>\n\t\t\tsource.pipe(\n\t\t\t\tgroupBy(key),\n\t\t\t\tmergeMap((g$) => g$.pipe(debounceTime(ms)))\n\t\t\t);\n\t}\n\n\t/**\n\t * Build the file system events observable stream\n\t */\n\tprivate buildFileSystemEvents$(): Observable<IndexerEvent> {\n\t\tconst created$ = this.fromVaultEvent(\"create\").pipe(this.toRelevantFiles());\n\t\tconst modified$ = this.fromVaultEvent(\"modify\").pipe(this.toRelevantFiles());\n\t\tconst deleted$ = this.fromVaultEvent(\"delete\").pipe(this.toRelevantFiles());\n\n\t\tconst renamed$ = fromEventPattern<[TAbstractFile, string]>(\n\t\t\t(handler) => this.vault.on(\"rename\", handler),\n\t\t\t(handler) => this.vault.off(\"rename\", handler)\n\t\t);\n\n\t\tconst changedIntents$ = merge(created$, modified$).pipe(\n\t\t\tthis.debounceByPath(this.config.debounceMs, (f) => f.path),\n\t\t\tmap((file): FileIntent => ({ kind: \"changed\", file, path: file.path }))\n\t\t);\n\n\t\tconst deletedIntents$ = deleted$.pipe(\n\t\t\tmap((file): FileIntent => ({ kind: \"deleted\", path: file.path }))\n\t\t);\n\n\t\tconst renamedIntents$ = renamed$.pipe(\n\t\t\tmap(([f, oldPath]) => [f, oldPath] as const),\n\t\t\tfilter(([f]) => Indexer.isMarkdownFile(f) && this.isRelevantFile(f)),\n\t\t\tmergeMap(([f, oldPath]) => [\n\t\t\t\t{ kind: \"deleted\", path: oldPath } as FileIntent,\n\t\t\t\t{ kind: \"changed\", file: f, path: f.path, oldPath } as FileIntent,\n\t\t\t])\n\t\t);\n\n\t\tconst intents$ = merge(changedIntents$, deletedIntents$, renamedIntents$);\n\n\t\treturn intents$.pipe(\n\t\t\tswitchMap((intent) => {\n\t\t\t\tif (intent.kind === \"deleted\") {\n\t\t\t\t\tthis.frontmatterCache.delete(intent.path);\n\t\t\t\t\treturn of<IndexerEvent>({ type: \"file-deleted\", filePath: intent.path });\n\t\t\t\t}\n\n\t\t\t\treturn from(this.buildEvent(intent.file, intent.oldPath)).pipe(\n\t\t\t\t\tfilter((e): e is IndexerEvent => e !== null)\n\t\t\t\t);\n\t\t\t})\n\t\t);\n\t}\n\n\t/**\n\t * Build an indexer event from a file\n\t */\n\tprivate async buildEvent(file: TFile, oldPath?: string): Promise<IndexerEvent | null> {\n\t\tconst cache = this.metadataCache.getFileCache(file);\n\t\tif (!cache || !cache.frontmatter) return null;\n\n\t\tconst { frontmatter } = cache;\n\t\tconst oldFrontmatter = this.frontmatterCache.get(file.path);\n\n\t\tconst source: FileSource = {\n\t\t\tfilePath: file.path,\n\t\t\tmtime: file.stat.mtime,\n\t\t\tfrontmatter,\n\t\t\tfolder: file.parent?.path || \"\",\n\t\t};\n\n\t\tconst event: IndexerEvent = {\n\t\t\ttype: \"file-changed\",\n\t\t\tfilePath: file.path,\n\t\t\toldPath,\n\t\t\tsource,\n\t\t\toldFrontmatter,\n\t\t\tfrontmatterDiff: oldFrontmatter\n\t\t\t\t? compareFrontmatter(oldFrontmatter, frontmatter, this.config.excludedDiffProps)\n\t\t\t\t: undefined,\n\t\t};\n\n\t\t// Update cache\n\t\tthis.frontmatterCache.set(file.path, { ...frontmatter });\n\n\t\treturn event;\n\t}\n\n\t/**\n\t * Check if file is in the configured directory\n\t */\n\tprivate isRelevantFile(file: TFile): boolean {\n\t\treturn isFileInConfiguredDirectory(file.path, this.config.directory);\n\t}\n}\n"]}
1
+ {"version":3,"file":"indexer.js","sourceRoot":"","sources":["../../src/core/indexer.ts"],"names":[],"mappings":"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","sourcesContent":["import { type App, type MetadataCache, type TAbstractFile, TFile, type Vault } from \"obsidian\";\nimport {\n\ttype BehaviorSubject,\n\tfrom,\n\tfromEventPattern,\n\tlastValueFrom,\n\tmerge,\n\ttype Observable,\n\tof,\n\tBehaviorSubject as RxBehaviorSubject,\n\tSubject,\n\ttype Subscription,\n} from \"rxjs\";\nimport { debounceTime, filter, groupBy, map, mergeMap, switchMap, toArray } from \"rxjs/operators\";\nimport { compareFrontmatter, type FrontmatterDiff } from \"../file/frontmatter-diff\";\n\n/**\n * Generic frontmatter object type for indexer\n */\nexport type IndexerFrontmatter = Record<string, unknown>;\n\n/**\n * Configuration for the generic indexer\n */\nexport interface IndexerConfig {\n\t/**\n\t * Function that determines whether a file should be included in the indexer.\n\t * Returns true if the file should be indexed, false otherwise.\n\t * If not provided, all files are included.\n\t */\n\tincludeFile?: (path: string) => boolean;\n\n\t/**\n\t * Properties to exclude when comparing frontmatter diffs\n\t */\n\texcludedDiffProps?: Set<string>;\n\n\t/**\n\t * Concurrency limit for file scanning operations\n\t */\n\tscanConcurrency?: number;\n\n\t/**\n\t * Debounce time in milliseconds for file change events\n\t */\n\tdebounceMs?: number;\n}\n\n/**\n * Raw file source with frontmatter and metadata\n */\nexport interface FileSource {\n\tfilePath: string;\n\tmtime: number;\n\tfrontmatter: IndexerFrontmatter;\n\tfolder: string;\n}\n\n/**\n * Types of indexer events\n */\nexport type IndexerEventType = \"file-changed\" | \"file-deleted\";\n\n/**\n * Generic indexer event\n */\nexport interface IndexerEvent {\n\ttype: IndexerEventType;\n\tfilePath: string;\n\toldPath?: string;\n\tsource?: FileSource;\n\toldFrontmatter?: IndexerFrontmatter;\n\tfrontmatterDiff?: FrontmatterDiff;\n}\n\ntype VaultEvent = \"create\" | \"modify\" | \"delete\" | \"rename\";\n\ntype FileIntent =\n\t| { kind: \"changed\"; file: TFile; path: string; oldPath?: string }\n\t| { kind: \"deleted\"; path: string };\n\n/**\n * Generic indexer that listens to Obsidian vault events and emits\n * RxJS observables with frontmatter diffs and metadata.\n *\n * This indexer is framework-agnostic and can be used by any plugin\n * that needs to track file changes with frontmatter.\n */\nexport class Indexer {\n\tprivate config: Required<IndexerConfig>;\n\tprivate fileSub: Subscription | null = null;\n\tprivate configSubscription: Subscription | null = null;\n\tprivate vault: Vault;\n\tprivate metadataCache: MetadataCache;\n\tprivate scanEventsSubject = new Subject<IndexerEvent>();\n\tprivate indexingCompleteSubject = new RxBehaviorSubject<boolean>(false);\n\tprivate frontmatterCache: Map<string, IndexerFrontmatter> = new Map();\n\n\tpublic readonly events$: Observable<IndexerEvent>;\n\tpublic readonly indexingComplete$: Observable<boolean>;\n\n\tconstructor(app: App, configStore: BehaviorSubject<IndexerConfig>) {\n\t\tthis.vault = app.vault;\n\t\tthis.metadataCache = app.metadataCache;\n\t\tthis.config = this.normalizeConfig(configStore.value);\n\n\t\tthis.configSubscription = configStore.subscribe((newConfig) => {\n\t\t\tconst includeFileChanged =\n\t\t\t\tthis.config.includeFile !== this.normalizeConfig(newConfig).includeFile;\n\t\t\tthis.config = this.normalizeConfig(newConfig);\n\n\t\t\tif (includeFileChanged) {\n\t\t\t\tthis.indexingCompleteSubject.next(false);\n\t\t\t\tvoid this.scanAllFiles();\n\t\t\t}\n\t\t});\n\n\t\tthis.events$ = this.scanEventsSubject.asObservable();\n\t\tthis.indexingComplete$ = this.indexingCompleteSubject.asObservable();\n\t}\n\n\tprivate normalizeConfig(config: IndexerConfig): Required<IndexerConfig> {\n\t\treturn {\n\t\t\tincludeFile: config.includeFile || (() => true),\n\t\t\texcludedDiffProps: config.excludedDiffProps || new Set(),\n\t\t\tscanConcurrency: config.scanConcurrency || 10,\n\t\t\tdebounceMs: config.debounceMs || 100,\n\t\t};\n\t}\n\n\tasync start(): Promise<void> {\n\t\tthis.indexingCompleteSubject.next(false);\n\n\t\tconst fileSystemEvents$ = this.buildFileSystemEvents$();\n\n\t\tthis.fileSub = fileSystemEvents$.subscribe((event) => {\n\t\t\tthis.scanEventsSubject.next(event);\n\t\t});\n\n\t\tawait this.scanAllFiles();\n\t}\n\n\tstop(): void {\n\t\tthis.fileSub?.unsubscribe();\n\t\tthis.fileSub = null;\n\t\tthis.configSubscription?.unsubscribe();\n\t\tthis.configSubscription = null;\n\t\tthis.indexingCompleteSubject.complete();\n\t}\n\n\tresync(): void {\n\t\tthis.frontmatterCache.clear();\n\t\tthis.indexingCompleteSubject.next(false);\n\t\tvoid this.scanAllFiles();\n\t}\n\n\t/**\n\t * Scan all markdown files in the configured directory\n\t */\n\tprivate async scanAllFiles(): Promise<void> {\n\t\tconst allFiles = this.vault.getMarkdownFiles();\n\t\tconst relevantFiles = allFiles.filter((file) => this.config.includeFile(file.path));\n\n\t\tconst events$ = from(relevantFiles).pipe(\n\t\t\tmergeMap(async (file) => {\n\t\t\t\ttry {\n\t\t\t\t\treturn await this.buildEvent(file);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(`Error processing file ${file.path}:`, error);\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t}, this.config.scanConcurrency),\n\t\t\tfilter((event): event is IndexerEvent => event !== null),\n\t\t\ttoArray()\n\t\t);\n\n\t\ttry {\n\t\t\tconst allEvents = await lastValueFrom(events$);\n\n\t\t\tfor (const event of allEvents) {\n\t\t\t\tthis.scanEventsSubject.next(event);\n\t\t\t}\n\n\t\t\tthis.indexingCompleteSubject.next(true);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"❌ Error during file scanning:\", error);\n\t\t\tthis.indexingCompleteSubject.next(true);\n\t\t}\n\t}\n\n\t/**\n\t * Create an observable from a vault event\n\t */\n\tprivate fromVaultEvent(eventName: VaultEvent): Observable<TAbstractFile> {\n\t\tif (eventName === \"create\") {\n\t\t\treturn fromEventPattern<TAbstractFile>(\n\t\t\t\t(handler) => this.vault.on(\"create\", handler),\n\t\t\t\t(handler) => this.vault.off(\"create\", handler)\n\t\t\t);\n\t\t}\n\n\t\tif (eventName === \"modify\") {\n\t\t\treturn fromEventPattern<TAbstractFile>(\n\t\t\t\t(handler) => this.vault.on(\"modify\", handler),\n\t\t\t\t(handler) => this.vault.off(\"modify\", handler)\n\t\t\t);\n\t\t}\n\n\t\tif (eventName === \"delete\") {\n\t\t\treturn fromEventPattern<TAbstractFile>(\n\t\t\t\t(handler) => this.vault.on(\"delete\", handler),\n\t\t\t\t(handler) => this.vault.off(\"delete\", handler)\n\t\t\t);\n\t\t}\n\n\t\t// eventName === \"rename\"\n\t\treturn fromEventPattern<[TAbstractFile, string]>(\n\t\t\t(handler) => this.vault.on(\"rename\", handler),\n\t\t\t(handler) => this.vault.off(\"rename\", handler)\n\t\t).pipe(map(([file]) => file));\n\t}\n\n\tprivate static isMarkdownFile(f: TAbstractFile): f is TFile {\n\t\treturn f instanceof TFile && f.extension === \"md\";\n\t}\n\n\t/**\n\t * Filter to only relevant markdown files in configured directory\n\t */\n\tprivate toRelevantFiles<T extends TAbstractFile>() {\n\t\treturn (source: Observable<T>) =>\n\t\t\tsource.pipe(\n\t\t\t\tfilter((f: TAbstractFile): f is TFile => Indexer.isMarkdownFile(f)),\n\t\t\t\tfilter((f) => this.config.includeFile(f.path))\n\t\t\t);\n\t}\n\n\t/**\n\t * Debounce events by file path\n\t */\n\tprivate debounceByPath<T>(ms: number, key: (x: T) => string) {\n\t\treturn (source: Observable<T>) =>\n\t\t\tsource.pipe(\n\t\t\t\tgroupBy(key),\n\t\t\t\tmergeMap((g$) => g$.pipe(debounceTime(ms)))\n\t\t\t);\n\t}\n\n\t/**\n\t * Build the file system events observable stream\n\t */\n\tprivate buildFileSystemEvents$(): Observable<IndexerEvent> {\n\t\tconst created$ = this.fromVaultEvent(\"create\").pipe(this.toRelevantFiles());\n\t\tconst modified$ = this.fromVaultEvent(\"modify\").pipe(this.toRelevantFiles());\n\t\tconst deleted$ = this.fromVaultEvent(\"delete\").pipe(this.toRelevantFiles());\n\n\t\tconst renamed$ = fromEventPattern<[TAbstractFile, string]>(\n\t\t\t(handler) => this.vault.on(\"rename\", handler),\n\t\t\t(handler) => this.vault.off(\"rename\", handler)\n\t\t);\n\n\t\tconst changedIntents$ = merge(created$, modified$).pipe(\n\t\t\tthis.debounceByPath(this.config.debounceMs, (f) => f.path),\n\t\t\tmap((file): FileIntent => ({ kind: \"changed\", file, path: file.path }))\n\t\t);\n\n\t\tconst deletedIntents$ = deleted$.pipe(\n\t\t\tmap((file): FileIntent => ({ kind: \"deleted\", path: file.path }))\n\t\t);\n\n\t\tconst renamedIntents$ = renamed$.pipe(\n\t\t\tmap(([f, oldPath]) => [f, oldPath] as const),\n\t\t\tfilter(([f]) => Indexer.isMarkdownFile(f) && this.config.includeFile(f.path)),\n\t\t\tmergeMap(([f, oldPath]) => [\n\t\t\t\t{ kind: \"deleted\", path: oldPath } as FileIntent,\n\t\t\t\t{ kind: \"changed\", file: f, path: f.path, oldPath } as FileIntent,\n\t\t\t])\n\t\t);\n\n\t\tconst intents$ = merge(changedIntents$, deletedIntents$, renamedIntents$);\n\n\t\treturn intents$.pipe(\n\t\t\tswitchMap((intent) => {\n\t\t\t\tif (intent.kind === \"deleted\") {\n\t\t\t\t\tthis.frontmatterCache.delete(intent.path);\n\t\t\t\t\treturn of<IndexerEvent>({ type: \"file-deleted\", filePath: intent.path });\n\t\t\t\t}\n\n\t\t\t\treturn from(this.buildEvent(intent.file, intent.oldPath)).pipe(\n\t\t\t\t\tfilter((e): e is IndexerEvent => e !== null)\n\t\t\t\t);\n\t\t\t})\n\t\t);\n\t}\n\n\t/**\n\t * Build an indexer event from a file\n\t */\n\tprivate async buildEvent(file: TFile, oldPath?: string): Promise<IndexerEvent | null> {\n\t\tconst cache = this.metadataCache.getFileCache(file);\n\t\tif (!cache || !cache.frontmatter) return null;\n\n\t\tconst { frontmatter } = cache;\n\t\tconst oldFrontmatter = this.frontmatterCache.get(file.path);\n\n\t\tconst source: FileSource = {\n\t\t\tfilePath: file.path,\n\t\t\tmtime: file.stat.mtime,\n\t\t\tfrontmatter,\n\t\t\tfolder: file.parent?.path || \"\",\n\t\t};\n\n\t\tconst event: IndexerEvent = {\n\t\t\ttype: \"file-changed\",\n\t\t\tfilePath: file.path,\n\t\t\toldPath,\n\t\t\tsource,\n\t\t\toldFrontmatter,\n\t\t\tfrontmatterDiff: oldFrontmatter\n\t\t\t\t? compareFrontmatter(oldFrontmatter, frontmatter, this.config.excludedDiffProps)\n\t\t\t\t: undefined,\n\t\t};\n\n\t\t// Update cache\n\t\tthis.frontmatterCache.set(file.path, { ...frontmatter });\n\n\t\treturn event;\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real1ty-obsidian-plugins/utils",
3
- "version": "2.16.0",
3
+ "version": "2.19.1",
4
4
  "description": "Shared utilities for Obsidian plugins",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -72,6 +72,12 @@ export abstract class BaseEvaluator<TRule extends BaseRule, TSettings> {
72
72
 
73
73
  return result === true;
74
74
  } catch (error) {
75
+ // Suppress ReferenceError logs - these occur when properties don't exist in frontmatter
76
+ // which is expected behavior (e.g., Status === 'Done' when Status is undefined)
77
+ if (error instanceof ReferenceError) {
78
+ return false;
79
+ }
80
+ // Log other errors (syntax errors, etc.) as they indicate actual problems
75
81
  console.warn(`Invalid expression (${rule.id}):`, rule.expression, error);
76
82
  return false;
77
83
  }
@@ -12,7 +12,6 @@ import {
12
12
  type Subscription,
13
13
  } from "rxjs";
14
14
  import { debounceTime, filter, groupBy, map, mergeMap, switchMap, toArray } from "rxjs/operators";
15
- import { isFileInConfiguredDirectory } from "../file/file";
16
15
  import { compareFrontmatter, type FrontmatterDiff } from "../file/frontmatter-diff";
17
16
 
18
17
  /**
@@ -25,10 +24,11 @@ export type IndexerFrontmatter = Record<string, unknown>;
25
24
  */
26
25
  export interface IndexerConfig {
27
26
  /**
28
- * Directory to scan for files (e.g., "Calendar", "Notes")
29
- * If empty string or undefined, scans entire vault
27
+ * Function that determines whether a file should be included in the indexer.
28
+ * Returns true if the file should be indexed, false otherwise.
29
+ * If not provided, all files are included.
30
30
  */
31
- directory?: string;
31
+ includeFile?: (path: string) => boolean;
32
32
 
33
33
  /**
34
34
  * Properties to exclude when comparing frontmatter diffs
@@ -99,31 +99,17 @@ export class Indexer {
99
99
  public readonly events$: Observable<IndexerEvent>;
100
100
  public readonly indexingComplete$: Observable<boolean>;
101
101
 
102
- constructor(_app: App, configStore: BehaviorSubject<IndexerConfig>) {
103
- this.vault = _app.vault;
104
- this.metadataCache = _app.metadataCache;
102
+ constructor(app: App, configStore: BehaviorSubject<IndexerConfig>) {
103
+ this.vault = app.vault;
104
+ this.metadataCache = app.metadataCache;
105
+ this.config = this.normalizeConfig(configStore.value);
105
106
 
106
- // Set defaults
107
- this.config = {
108
- directory: configStore.value.directory || "",
109
- excludedDiffProps: configStore.value.excludedDiffProps || new Set(),
110
- scanConcurrency: configStore.value.scanConcurrency || 10,
111
- debounceMs: configStore.value.debounceMs || 100,
112
- };
113
-
114
- // Subscribe to config changes
115
107
  this.configSubscription = configStore.subscribe((newConfig) => {
116
- const directoryChanged = this.config.directory !== (newConfig.directory || "");
117
-
118
- this.config = {
119
- directory: newConfig.directory || "",
120
- excludedDiffProps: newConfig.excludedDiffProps || new Set(),
121
- scanConcurrency: newConfig.scanConcurrency || 10,
122
- debounceMs: newConfig.debounceMs || 100,
123
- };
108
+ const includeFileChanged =
109
+ this.config.includeFile !== this.normalizeConfig(newConfig).includeFile;
110
+ this.config = this.normalizeConfig(newConfig);
124
111
 
125
- // Rescan if directory changed
126
- if (directoryChanged) {
112
+ if (includeFileChanged) {
127
113
  this.indexingCompleteSubject.next(false);
128
114
  void this.scanAllFiles();
129
115
  }
@@ -133,9 +119,15 @@ export class Indexer {
133
119
  this.indexingComplete$ = this.indexingCompleteSubject.asObservable();
134
120
  }
135
121
 
136
- /**
137
- * Start the indexer and perform initial scan
138
- */
122
+ private normalizeConfig(config: IndexerConfig): Required<IndexerConfig> {
123
+ return {
124
+ includeFile: config.includeFile || (() => true),
125
+ excludedDiffProps: config.excludedDiffProps || new Set(),
126
+ scanConcurrency: config.scanConcurrency || 10,
127
+ debounceMs: config.debounceMs || 100,
128
+ };
129
+ }
130
+
139
131
  async start(): Promise<void> {
140
132
  this.indexingCompleteSubject.next(false);
141
133
 
@@ -148,9 +140,6 @@ export class Indexer {
148
140
  await this.scanAllFiles();
149
141
  }
150
142
 
151
- /**
152
- * Stop the indexer and clean up subscriptions
153
- */
154
143
  stop(): void {
155
144
  this.fileSub?.unsubscribe();
156
145
  this.fileSub = null;
@@ -159,9 +148,6 @@ export class Indexer {
159
148
  this.indexingCompleteSubject.complete();
160
149
  }
161
150
 
162
- /**
163
- * Clear cache and rescan all files
164
- */
165
151
  resync(): void {
166
152
  this.frontmatterCache.clear();
167
153
  this.indexingCompleteSubject.next(false);
@@ -173,7 +159,7 @@ export class Indexer {
173
159
  */
174
160
  private async scanAllFiles(): Promise<void> {
175
161
  const allFiles = this.vault.getMarkdownFiles();
176
- const relevantFiles = allFiles.filter((file) => this.isRelevantFile(file));
162
+ const relevantFiles = allFiles.filter((file) => this.config.includeFile(file.path));
177
163
 
178
164
  const events$ = from(relevantFiles).pipe(
179
165
  mergeMap(async (file) => {
@@ -234,9 +220,6 @@ export class Indexer {
234
220
  ).pipe(map(([file]) => file));
235
221
  }
236
222
 
237
- /**
238
- * Type guard to check if file is a markdown file
239
- */
240
223
  private static isMarkdownFile(f: TAbstractFile): f is TFile {
241
224
  return f instanceof TFile && f.extension === "md";
242
225
  }
@@ -248,7 +231,7 @@ export class Indexer {
248
231
  return (source: Observable<T>) =>
249
232
  source.pipe(
250
233
  filter((f: TAbstractFile): f is TFile => Indexer.isMarkdownFile(f)),
251
- filter((f) => this.isRelevantFile(f))
234
+ filter((f) => this.config.includeFile(f.path))
252
235
  );
253
236
  }
254
237
 
@@ -287,7 +270,7 @@ export class Indexer {
287
270
 
288
271
  const renamedIntents$ = renamed$.pipe(
289
272
  map(([f, oldPath]) => [f, oldPath] as const),
290
- filter(([f]) => Indexer.isMarkdownFile(f) && this.isRelevantFile(f)),
273
+ filter(([f]) => Indexer.isMarkdownFile(f) && this.config.includeFile(f.path)),
291
274
  mergeMap(([f, oldPath]) => [
292
275
  { kind: "deleted", path: oldPath } as FileIntent,
293
276
  { kind: "changed", file: f, path: f.path, oldPath } as FileIntent,
@@ -343,11 +326,4 @@ export class Indexer {
343
326
 
344
327
  return event;
345
328
  }
346
-
347
- /**
348
- * Check if file is in the configured directory
349
- */
350
- private isRelevantFile(file: TFile): boolean {
351
- return isFileInConfiguredDirectory(file.path, this.config.directory);
352
- }
353
329
  }