@karmaniverous/jeeves-watcher 0.8.3 → 0.8.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.
@@ -621,18 +621,23 @@ class ValuesManager extends JsonFileStore {
621
621
  index[ruleName] ??= {};
622
622
  const ruleValues = index[ruleName];
623
623
  for (const [key, value] of Object.entries(metadata)) {
624
- if (!this.isTrackable(value))
625
- continue;
626
- ruleValues[key] ??= [];
627
- const arr = ruleValues[key];
628
- if (!arr.includes(value)) {
629
- arr.push(value);
630
- arr.sort((a, b) => {
631
- if (typeof a === typeof b) {
632
- return String(a).localeCompare(String(b));
633
- }
634
- return typeof a < typeof b ? -1 : 1;
635
- });
624
+ // Decompose arrays into individual trackable elements so that
625
+ // array-typed fields (e.g. domains: ["email"]) are indexed.
626
+ const items = Array.isArray(value) ? value : [value];
627
+ for (const item of items) {
628
+ if (!this.isTrackable(item))
629
+ continue;
630
+ ruleValues[key] ??= [];
631
+ const arr = ruleValues[key];
632
+ if (!arr.includes(item)) {
633
+ arr.push(item);
634
+ arr.sort((a, b) => {
635
+ if (typeof a === typeof b) {
636
+ return String(a).localeCompare(String(b));
637
+ }
638
+ return typeof a < typeof b ? -1 : 1;
639
+ });
640
+ }
636
641
  }
637
642
  }
638
643
  this.save();
@@ -1491,6 +1496,31 @@ function validateSchemaCompleteness(schema, ruleName) {
1491
1496
  }
1492
1497
  }
1493
1498
  }
1499
+ /** Types that can produce trackable facet values. */
1500
+ const FACETABLE_TYPES = new Set([
1501
+ 'string',
1502
+ 'number',
1503
+ 'boolean',
1504
+ 'integer',
1505
+ 'array',
1506
+ ]);
1507
+ /**
1508
+ * Validate that uiHint and enum are only applied to facetable property types.
1509
+ * Throws if a non-facetable type (e.g. object) declares uiHint or enum.
1510
+ *
1511
+ * @param schema - Resolved schema to validate.
1512
+ * @param ruleName - Name of the rule (for error messages).
1513
+ */
1514
+ function validateFacetTypes(schema, ruleName) {
1515
+ for (const [propName, propDef] of Object.entries(schema.properties)) {
1516
+ if (!propDef.type)
1517
+ continue;
1518
+ if (!FACETABLE_TYPES.has(propDef.type) &&
1519
+ (propDef.uiHint !== undefined || propDef.enum !== undefined)) {
1520
+ throw new Error(`Property "${propName}" in rule "${ruleName}" has type "${propDef.type}" with uiHint/enum. Facet hints are only valid on string, number, boolean, integer, or array types.`);
1521
+ }
1522
+ }
1523
+ }
1494
1524
 
1495
1525
  /**
1496
1526
  * @module rules/apply
@@ -1557,6 +1587,7 @@ async function applyRules(compiledRules, attributes, options = {}) {
1557
1587
  });
1558
1588
  // Validate schema completeness
1559
1589
  validateSchemaCompleteness(mergedSchema, rule.name);
1590
+ validateFacetTypes(mergedSchema, rule.name);
1560
1591
  // Resolve and coerce metadata
1561
1592
  const schemaOutput = resolveAndCoerce(mergedSchema, attributes, hbs);
1562
1593
  merged = { ...merged, ...schemaOutput };
@@ -2668,6 +2699,7 @@ function validateInferenceRuleSchemas(parsed) {
2668
2699
  globalSchemas: parsed.schemas,
2669
2700
  });
2670
2701
  validateSchemaCompleteness(merged, rule.name);
2702
+ validateFacetTypes(merged, rule.name);
2671
2703
  }
2672
2704
  catch (error) {
2673
2705
  return [
@@ -2748,6 +2780,9 @@ function computeRulesHash(rules) {
2748
2780
  * A property is facetable if it declares `uiHint` or `enum`.
2749
2781
  */
2750
2782
  function isFacetable(prop) {
2783
+ // Reject non-primitive types that can never produce trackable values.
2784
+ if (prop.type === 'object')
2785
+ return false;
2751
2786
  return prop.uiHint !== undefined || prop.enum !== undefined;
2752
2787
  }
2753
2788
  /**
@@ -5987,6 +6022,9 @@ class JeevesWatcher {
5987
6022
  const { templateEngine, customMapLib } = await buildTemplateEngineAndCustomMapLib(this.config, configDir);
5988
6023
  this.helperIntrospection = await introspectHelpers(this.config, configDir);
5989
6024
  const processorConfig = createProcessorConfig(this.config, configDir, customMapLib);
6025
+ const stateDir = this.config.stateDir ?? this.config.metadataDir ?? '.jeeves-metadata';
6026
+ this.issuesManager = new IssuesManager(stateDir, logger);
6027
+ this.valuesManager = new ValuesManager(stateDir, logger);
5990
6028
  const processor = this.factories.createDocumentProcessor({
5991
6029
  config: processorConfig,
5992
6030
  embeddingProvider,
@@ -5994,6 +6032,8 @@ class JeevesWatcher {
5994
6032
  compiledRules,
5995
6033
  logger,
5996
6034
  templateEngine,
6035
+ issuesManager: this.issuesManager,
6036
+ valuesManager: this.valuesManager,
5997
6037
  });
5998
6038
  this.processor = processor;
5999
6039
  this.queue = this.factories.createEventQueue({
@@ -6002,9 +6042,6 @@ class JeevesWatcher {
6002
6042
  rateLimitPerMinute: this.config.embedding.rateLimitPerMinute,
6003
6043
  });
6004
6044
  this.watcher = createWatcher(this.config, this.factories, this.queue, processor, logger, this.runtimeOptions);
6005
- const stateDir = this.config.stateDir ?? this.config.metadataDir ?? '.jeeves-metadata';
6006
- this.issuesManager = new IssuesManager(stateDir, logger);
6007
- this.valuesManager = new ValuesManager(stateDir, logger);
6008
6045
  this.server = await this.startApiServer();
6009
6046
  this.watcher.start();
6010
6047
  this.startConfigWatch();
package/dist/index.js CHANGED
@@ -846,6 +846,31 @@ function validateSchemaCompleteness(schema, ruleName) {
846
846
  }
847
847
  }
848
848
  }
849
+ /** Types that can produce trackable facet values. */
850
+ const FACETABLE_TYPES = new Set([
851
+ 'string',
852
+ 'number',
853
+ 'boolean',
854
+ 'integer',
855
+ 'array',
856
+ ]);
857
+ /**
858
+ * Validate that uiHint and enum are only applied to facetable property types.
859
+ * Throws if a non-facetable type (e.g. object) declares uiHint or enum.
860
+ *
861
+ * @param schema - Resolved schema to validate.
862
+ * @param ruleName - Name of the rule (for error messages).
863
+ */
864
+ function validateFacetTypes(schema, ruleName) {
865
+ for (const [propName, propDef] of Object.entries(schema.properties)) {
866
+ if (!propDef.type)
867
+ continue;
868
+ if (!FACETABLE_TYPES.has(propDef.type) &&
869
+ (propDef.uiHint !== undefined || propDef.enum !== undefined)) {
870
+ throw new Error(`Property "${propName}" in rule "${ruleName}" has type "${propDef.type}" with uiHint/enum. Facet hints are only valid on string, number, boolean, integer, or array types.`);
871
+ }
872
+ }
873
+ }
849
874
 
850
875
  /**
851
876
  * @module rules/apply
@@ -912,6 +937,7 @@ async function applyRules(compiledRules, attributes, options = {}) {
912
937
  });
913
938
  // Validate schema completeness
914
939
  validateSchemaCompleteness(mergedSchema, rule.name);
940
+ validateFacetTypes(mergedSchema, rule.name);
915
941
  // Resolve and coerce metadata
916
942
  const schemaOutput = resolveAndCoerce(mergedSchema, attributes, hbs);
917
943
  merged = { ...merged, ...schemaOutput };
@@ -2359,6 +2385,7 @@ function validateInferenceRuleSchemas(parsed) {
2359
2385
  globalSchemas: parsed.schemas,
2360
2386
  });
2361
2387
  validateSchemaCompleteness(merged, rule.name);
2388
+ validateFacetTypes(merged, rule.name);
2362
2389
  }
2363
2390
  catch (error) {
2364
2391
  return [
@@ -2439,6 +2466,9 @@ function computeRulesHash(rules) {
2439
2466
  * A property is facetable if it declares `uiHint` or `enum`.
2440
2467
  */
2441
2468
  function isFacetable(prop) {
2469
+ // Reject non-primitive types that can never produce trackable values.
2470
+ if (prop.type === 'object')
2471
+ return false;
2442
2472
  return prop.uiHint !== undefined || prop.enum !== undefined;
2443
2473
  }
2444
2474
  /**
@@ -3509,18 +3539,23 @@ class ValuesManager extends JsonFileStore {
3509
3539
  index[ruleName] ??= {};
3510
3540
  const ruleValues = index[ruleName];
3511
3541
  for (const [key, value] of Object.entries(metadata)) {
3512
- if (!this.isTrackable(value))
3513
- continue;
3514
- ruleValues[key] ??= [];
3515
- const arr = ruleValues[key];
3516
- if (!arr.includes(value)) {
3517
- arr.push(value);
3518
- arr.sort((a, b) => {
3519
- if (typeof a === typeof b) {
3520
- return String(a).localeCompare(String(b));
3521
- }
3522
- return typeof a < typeof b ? -1 : 1;
3523
- });
3542
+ // Decompose arrays into individual trackable elements so that
3543
+ // array-typed fields (e.g. domains: ["email"]) are indexed.
3544
+ const items = Array.isArray(value) ? value : [value];
3545
+ for (const item of items) {
3546
+ if (!this.isTrackable(item))
3547
+ continue;
3548
+ ruleValues[key] ??= [];
3549
+ const arr = ruleValues[key];
3550
+ if (!arr.includes(item)) {
3551
+ arr.push(item);
3552
+ arr.sort((a, b) => {
3553
+ if (typeof a === typeof b) {
3554
+ return String(a).localeCompare(String(b));
3555
+ }
3556
+ return typeof a < typeof b ? -1 : 1;
3557
+ });
3558
+ }
3524
3559
  }
3525
3560
  }
3526
3561
  this.save();
@@ -5965,6 +6000,9 @@ class JeevesWatcher {
5965
6000
  const { templateEngine, customMapLib } = await buildTemplateEngineAndCustomMapLib(this.config, configDir);
5966
6001
  this.helperIntrospection = await introspectHelpers(this.config, configDir);
5967
6002
  const processorConfig = createProcessorConfig(this.config, configDir, customMapLib);
6003
+ const stateDir = this.config.stateDir ?? this.config.metadataDir ?? '.jeeves-metadata';
6004
+ this.issuesManager = new IssuesManager(stateDir, logger);
6005
+ this.valuesManager = new ValuesManager(stateDir, logger);
5968
6006
  const processor = this.factories.createDocumentProcessor({
5969
6007
  config: processorConfig,
5970
6008
  embeddingProvider,
@@ -5972,6 +6010,8 @@ class JeevesWatcher {
5972
6010
  compiledRules,
5973
6011
  logger,
5974
6012
  templateEngine,
6013
+ issuesManager: this.issuesManager,
6014
+ valuesManager: this.valuesManager,
5975
6015
  });
5976
6016
  this.processor = processor;
5977
6017
  this.queue = this.factories.createEventQueue({
@@ -5980,9 +6020,6 @@ class JeevesWatcher {
5980
6020
  rateLimitPerMinute: this.config.embedding.rateLimitPerMinute,
5981
6021
  });
5982
6022
  this.watcher = createWatcher(this.config, this.factories, this.queue, processor, logger, this.runtimeOptions);
5983
- const stateDir = this.config.stateDir ?? this.config.metadataDir ?? '.jeeves-metadata';
5984
- this.issuesManager = new IssuesManager(stateDir, logger);
5985
- this.valuesManager = new ValuesManager(stateDir, logger);
5986
6023
  this.server = await this.startApiServer();
5987
6024
  this.watcher.start();
5988
6025
  this.startConfigWatch();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-watcher",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "author": "Jason Williscroft",
5
5
  "description": "Filesystem watcher that keeps a Qdrant vector store in sync with document changes",
6
6
  "license": "BSD-3-Clause",
@@ -84,22 +84,22 @@
84
84
  "zod": "^4.3.6"
85
85
  },
86
86
  "devDependencies": {
87
- "@dotenvx/dotenvx": "^1.52.0",
87
+ "@dotenvx/dotenvx": "^1.54.1",
88
88
  "@rollup/plugin-alias": "^6.0.0",
89
- "@rollup/plugin-commonjs": "^29.0.0",
89
+ "@rollup/plugin-commonjs": "^29.0.2",
90
90
  "@rollup/plugin-json": "^6.1.0",
91
91
  "@rollup/plugin-node-resolve": "^16.0.3",
92
92
  "@rollup/plugin-typescript": "^12.3.0",
93
93
  "@types/fs-extra": "^11.0.4",
94
94
  "@types/js-yaml": "*",
95
- "@types/node": "^25.3.0",
95
+ "@types/node": "^25.3.5",
96
96
  "@types/picomatch": "^4.0.2",
97
97
  "@types/uuid": "*",
98
98
  "@vitest/coverage-v8": "^4.0.18",
99
99
  "auto-changelog": "^2.5.0",
100
100
  "cross-env": "^10.1.0",
101
- "fs-extra": "^11.3.3",
102
- "happy-dom": "^20.7.0",
101
+ "fs-extra": "^11.3.4",
102
+ "happy-dom": "^20.8.3",
103
103
  "knip": "^5.85.0",
104
104
  "release-it": "^19.2.4",
105
105
  "rollup": "^4.59.0",