@soulcraft/brainy 3.50.1 → 4.0.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 (58) hide show
  1. package/CHANGELOG.md +242 -0
  2. package/README.md +358 -658
  3. package/dist/api/ConfigAPI.js +56 -19
  4. package/dist/api/DataAPI.js +24 -18
  5. package/dist/augmentations/storageAugmentations.d.ts +24 -0
  6. package/dist/augmentations/storageAugmentations.js +22 -0
  7. package/dist/brainy.js +32 -9
  8. package/dist/cli/commands/core.d.ts +20 -10
  9. package/dist/cli/commands/core.js +384 -82
  10. package/dist/cli/commands/import.d.ts +41 -0
  11. package/dist/cli/commands/import.js +456 -0
  12. package/dist/cli/commands/insights.d.ts +34 -0
  13. package/dist/cli/commands/insights.js +300 -0
  14. package/dist/cli/commands/neural.d.ts +6 -12
  15. package/dist/cli/commands/neural.js +113 -10
  16. package/dist/cli/commands/nlp.d.ts +28 -0
  17. package/dist/cli/commands/nlp.js +246 -0
  18. package/dist/cli/commands/storage.d.ts +64 -0
  19. package/dist/cli/commands/storage.js +730 -0
  20. package/dist/cli/index.js +210 -24
  21. package/dist/coreTypes.d.ts +206 -34
  22. package/dist/distributed/configManager.js +8 -6
  23. package/dist/distributed/shardMigration.js +2 -0
  24. package/dist/distributed/storageDiscovery.js +6 -4
  25. package/dist/embeddings/EmbeddingManager.d.ts +2 -2
  26. package/dist/embeddings/EmbeddingManager.js +5 -1
  27. package/dist/graph/lsm/LSMTree.js +32 -20
  28. package/dist/hnsw/typeAwareHNSWIndex.js +6 -2
  29. package/dist/storage/adapters/azureBlobStorage.d.ts +545 -0
  30. package/dist/storage/adapters/azureBlobStorage.js +1809 -0
  31. package/dist/storage/adapters/baseStorageAdapter.d.ts +16 -13
  32. package/dist/storage/adapters/fileSystemStorage.d.ts +21 -9
  33. package/dist/storage/adapters/fileSystemStorage.js +204 -127
  34. package/dist/storage/adapters/gcsStorage.d.ts +119 -9
  35. package/dist/storage/adapters/gcsStorage.js +317 -62
  36. package/dist/storage/adapters/memoryStorage.d.ts +30 -18
  37. package/dist/storage/adapters/memoryStorage.js +99 -94
  38. package/dist/storage/adapters/opfsStorage.d.ts +48 -10
  39. package/dist/storage/adapters/opfsStorage.js +201 -80
  40. package/dist/storage/adapters/r2Storage.d.ts +12 -5
  41. package/dist/storage/adapters/r2Storage.js +63 -15
  42. package/dist/storage/adapters/s3CompatibleStorage.d.ts +164 -17
  43. package/dist/storage/adapters/s3CompatibleStorage.js +472 -80
  44. package/dist/storage/adapters/typeAwareStorageAdapter.d.ts +38 -6
  45. package/dist/storage/adapters/typeAwareStorageAdapter.js +218 -39
  46. package/dist/storage/baseStorage.d.ts +41 -38
  47. package/dist/storage/baseStorage.js +110 -134
  48. package/dist/storage/storageFactory.d.ts +29 -2
  49. package/dist/storage/storageFactory.js +30 -1
  50. package/dist/utils/entityIdMapper.js +5 -2
  51. package/dist/utils/fieldTypeInference.js +8 -1
  52. package/dist/utils/metadataFilter.d.ts +3 -2
  53. package/dist/utils/metadataFilter.js +1 -0
  54. package/dist/utils/metadataIndex.d.ts +2 -1
  55. package/dist/utils/metadataIndex.js +9 -1
  56. package/dist/utils/metadataIndexChunking.js +9 -4
  57. package/dist/utils/periodicCleanup.js +1 -0
  58. package/package.json +3 -1
@@ -7,16 +7,19 @@ import { StorageCompatibilityLayer } from '../backwardCompatibility.js';
7
7
  // Node.js modules - dynamically imported to avoid issues in browser environments
8
8
  let fs;
9
9
  let path;
10
+ let zlib;
10
11
  let moduleLoadingPromise = null;
11
12
  // Try to load Node.js modules
12
13
  try {
13
14
  // Using dynamic imports to avoid issues in browser environments
14
15
  const fsPromise = import('node:fs');
15
16
  const pathPromise = import('node:path');
16
- moduleLoadingPromise = Promise.all([fsPromise, pathPromise])
17
- .then(([fsModule, pathModule]) => {
17
+ const zlibPromise = import('node:zlib');
18
+ moduleLoadingPromise = Promise.all([fsPromise, pathPromise, zlibPromise])
19
+ .then(([fsModule, pathModule, zlibModule]) => {
18
20
  fs = fsModule;
19
21
  path = pathModule.default;
22
+ zlib = zlibModule;
20
23
  })
21
24
  .catch((error) => {
22
25
  console.error('Failed to load Node.js modules:', error);
@@ -34,8 +37,9 @@ export class FileSystemStorage extends BaseStorage {
34
37
  /**
35
38
  * Initialize the storage adapter
36
39
  * @param rootDirectory The root directory for storage
40
+ * @param options Optional configuration
37
41
  */
38
- constructor(rootDirectory) {
42
+ constructor(rootDirectory, options) {
39
43
  super();
40
44
  // Fixed sharding configuration for optimal balance of simplicity and performance
41
45
  // Single-level sharding (depth=1) provides excellent performance for 1-2.5M entities
@@ -50,7 +54,17 @@ export class FileSystemStorage extends BaseStorage {
50
54
  this.activeLocks = new Set();
51
55
  this.lockTimers = new Map(); // Track timers for cleanup
52
56
  this.allTimers = new Set(); // Track all timers for cleanup
57
+ // Compression configuration (v4.0.0)
58
+ this.compressionEnabled = true; // Enable gzip compression by default for 60-80% disk savings
59
+ this.compressionLevel = 6; // zlib compression level (1-9, default: 6 = balanced)
53
60
  this.rootDir = rootDirectory;
61
+ // Configure compression
62
+ if (options?.compression !== undefined) {
63
+ this.compressionEnabled = options.compression;
64
+ }
65
+ if (options?.compressionLevel !== undefined) {
66
+ this.compressionLevel = Math.min(9, Math.max(1, options.compressionLevel));
67
+ }
54
68
  // Defer path operations until init() when path module is guaranteed to be loaded
55
69
  }
56
70
  /**
@@ -180,9 +194,11 @@ export class FileSystemStorage extends BaseStorage {
180
194
  const filePath = this.getNodePath(node.id);
181
195
  await this.ensureDirectoryExists(path.dirname(filePath));
182
196
  await fs.promises.writeFile(filePath, JSON.stringify(serializableNode, null, 2));
183
- // Update counts for new nodes (intelligent type detection)
197
+ // Update counts for new nodes (v4.0.0: load metadata separately)
184
198
  if (isNew) {
185
- const type = node.metadata?.type || node.metadata?.nounType || 'default';
199
+ // v4.0.0: Get type from separate metadata storage
200
+ const metadata = await this.getNounMetadata(node.id);
201
+ const type = metadata?.noun || 'default';
186
202
  this.incrementEntityCount(type);
187
203
  // Persist counts periodically (every 10 operations for efficiency)
188
204
  if (this.totalNounCount % 10 === 0) {
@@ -308,16 +324,16 @@ export class FileSystemStorage extends BaseStorage {
308
324
  async deleteNode(id) {
309
325
  await this.ensureInitialized();
310
326
  const filePath = this.getNodePath(id);
311
- // Load node to get type for count update
327
+ // Load metadata to get type for count update (v4.0.0: separate storage)
312
328
  try {
313
- const node = await this.getNode(id);
314
- if (node) {
315
- const type = node.metadata?.type || node.metadata?.nounType || 'default';
329
+ const metadata = await this.getNounMetadata(id);
330
+ if (metadata) {
331
+ const type = metadata.noun || 'default';
316
332
  this.decrementEntityCount(type);
317
333
  }
318
334
  }
319
335
  catch {
320
- // Node might not exist, that's ok
336
+ // Metadata might not exist, that's ok
321
337
  }
322
338
  try {
323
339
  await fs.promises.unlink(filePath);
@@ -380,7 +396,7 @@ export class FileSystemStorage extends BaseStorage {
380
396
  for (const [level, nodeIds] of Object.entries(parsedEdge.connections)) {
381
397
  connections.set(Number(level), new Set(nodeIds));
382
398
  }
383
- // ARCHITECTURAL FIX (v3.50.1): Return HNSWVerb with core relational fields
399
+ // v4.0.0: Return HNSWVerb with core relational fields (NO metadata field)
384
400
  return {
385
401
  id: parsedEdge.id,
386
402
  vector: parsedEdge.vector,
@@ -388,9 +404,9 @@ export class FileSystemStorage extends BaseStorage {
388
404
  // CORE RELATIONAL DATA (read from vector file)
389
405
  verb: parsedEdge.verb,
390
406
  sourceId: parsedEdge.sourceId,
391
- targetId: parsedEdge.targetId,
392
- // User metadata (retrieved separately via getVerbMetadata())
393
- metadata: parsedEdge.metadata
407
+ targetId: parsedEdge.targetId
408
+ // NO metadata field in v4.0.0
409
+ // User metadata retrieved separately via getVerbMetadata()
394
410
  };
395
411
  }
396
412
  catch (error) {
@@ -423,7 +439,7 @@ export class FileSystemStorage extends BaseStorage {
423
439
  for (const [level, nodeIds] of Object.entries(parsedEdge.connections)) {
424
440
  connections.set(Number(level), new Set(nodeIds));
425
441
  }
426
- // ARCHITECTURAL FIX (v3.50.1): Include core relational fields
442
+ // v4.0.0: Include core relational fields (NO metadata field)
427
443
  allEdges.push({
428
444
  id: parsedEdge.id,
429
445
  vector: parsedEdge.vector,
@@ -431,9 +447,9 @@ export class FileSystemStorage extends BaseStorage {
431
447
  // CORE RELATIONAL DATA
432
448
  verb: parsedEdge.verb,
433
449
  sourceId: parsedEdge.sourceId,
434
- targetId: parsedEdge.targetId,
435
- // User metadata
436
- metadata: parsedEdge.metadata
450
+ targetId: parsedEdge.targetId
451
+ // NO metadata field in v4.0.0
452
+ // User metadata retrieved separately via getVerbMetadata()
437
453
  });
438
454
  }
439
455
  }
@@ -492,7 +508,7 @@ export class FileSystemStorage extends BaseStorage {
492
508
  try {
493
509
  const metadata = await this.getVerbMetadata(id);
494
510
  if (metadata) {
495
- const verbType = metadata.verb || metadata.type || 'default';
511
+ const verbType = (metadata.verb || metadata.type || 'default');
496
512
  this.decrementVerbCount(verbType);
497
513
  await this.deleteVerbMetadata(id);
498
514
  }
@@ -505,21 +521,71 @@ export class FileSystemStorage extends BaseStorage {
505
521
  /**
506
522
  * Primitive operation: Write object to path
507
523
  * All metadata operations use this internally via base class routing
524
+ * v4.0.0: Supports gzip compression for 60-80% disk savings
508
525
  */
509
526
  async writeObjectToPath(pathStr, data) {
510
527
  await this.ensureInitialized();
511
528
  const fullPath = path.join(this.rootDir, pathStr);
512
529
  await this.ensureDirectoryExists(path.dirname(fullPath));
513
- await fs.promises.writeFile(fullPath, JSON.stringify(data, null, 2));
530
+ if (this.compressionEnabled) {
531
+ // Write compressed data with .gz extension
532
+ const compressedPath = `${fullPath}.gz`;
533
+ const jsonString = JSON.stringify(data, null, 2);
534
+ const compressed = await new Promise((resolve, reject) => {
535
+ zlib.gzip(Buffer.from(jsonString, 'utf-8'), { level: this.compressionLevel }, (err, result) => {
536
+ if (err)
537
+ reject(err);
538
+ else
539
+ resolve(result);
540
+ });
541
+ });
542
+ await fs.promises.writeFile(compressedPath, compressed);
543
+ // Clean up uncompressed file if it exists (migration from uncompressed)
544
+ try {
545
+ await fs.promises.unlink(fullPath);
546
+ }
547
+ catch (error) {
548
+ // Ignore if file doesn't exist
549
+ if (error.code !== 'ENOENT') {
550
+ console.warn(`Failed to remove uncompressed file ${fullPath}:`, error);
551
+ }
552
+ }
553
+ }
554
+ else {
555
+ // Write uncompressed data
556
+ await fs.promises.writeFile(fullPath, JSON.stringify(data, null, 2));
557
+ }
514
558
  }
515
559
  /**
516
560
  * Primitive operation: Read object from path
517
561
  * All metadata operations use this internally via base class routing
518
562
  * Enhanced error handling for corrupted metadata files (Bug #3 mitigation)
563
+ * v4.0.0: Supports reading both compressed (.gz) and uncompressed files for backward compatibility
519
564
  */
520
565
  async readObjectFromPath(pathStr) {
521
566
  await this.ensureInitialized();
522
567
  const fullPath = path.join(this.rootDir, pathStr);
568
+ const compressedPath = `${fullPath}.gz`;
569
+ // Try reading compressed file first (if compression is enabled or file exists)
570
+ try {
571
+ const compressedData = await fs.promises.readFile(compressedPath);
572
+ const decompressed = await new Promise((resolve, reject) => {
573
+ zlib.gunzip(compressedData, (err, result) => {
574
+ if (err)
575
+ reject(err);
576
+ else
577
+ resolve(result);
578
+ });
579
+ });
580
+ return JSON.parse(decompressed.toString('utf-8'));
581
+ }
582
+ catch (error) {
583
+ // If compressed file doesn't exist, fall back to uncompressed
584
+ if (error.code !== 'ENOENT') {
585
+ console.warn(`Failed to read compressed file ${compressedPath}:`, error);
586
+ }
587
+ }
588
+ // Fall back to reading uncompressed file (for backward compatibility)
523
589
  try {
524
590
  const data = await fs.promises.readFile(fullPath, 'utf-8');
525
591
  return JSON.parse(data);
@@ -542,33 +608,71 @@ export class FileSystemStorage extends BaseStorage {
542
608
  /**
543
609
  * Primitive operation: Delete object from path
544
610
  * All metadata operations use this internally via base class routing
611
+ * v4.0.0: Deletes both compressed and uncompressed versions (for cleanup)
545
612
  */
546
613
  async deleteObjectFromPath(pathStr) {
547
614
  await this.ensureInitialized();
548
615
  const fullPath = path.join(this.rootDir, pathStr);
616
+ const compressedPath = `${fullPath}.gz`;
617
+ // Try deleting both compressed and uncompressed files (for cleanup during migration)
618
+ let deletedCount = 0;
619
+ // Delete compressed file
620
+ try {
621
+ await fs.promises.unlink(compressedPath);
622
+ deletedCount++;
623
+ }
624
+ catch (error) {
625
+ if (error.code !== 'ENOENT') {
626
+ console.warn(`Error deleting compressed file ${compressedPath}:`, error);
627
+ }
628
+ }
629
+ // Delete uncompressed file
549
630
  try {
550
631
  await fs.promises.unlink(fullPath);
632
+ deletedCount++;
551
633
  }
552
634
  catch (error) {
553
635
  if (error.code !== 'ENOENT') {
554
- console.error(`Error deleting object from ${pathStr}:`, error);
636
+ console.error(`Error deleting uncompressed file ${pathStr}:`, error);
555
637
  throw error;
556
638
  }
557
639
  }
640
+ // If neither file existed, it's not an error (already deleted)
641
+ if (deletedCount === 0) {
642
+ // File doesn't exist - this is fine
643
+ }
558
644
  }
559
645
  /**
560
646
  * Primitive operation: List objects under path prefix
561
647
  * All metadata operations use this internally via base class routing
648
+ * v4.0.0: Handles both .json and .json.gz files, normalizes paths
562
649
  */
563
650
  async listObjectsUnderPath(prefix) {
564
651
  await this.ensureInitialized();
565
652
  const fullPath = path.join(this.rootDir, prefix);
566
653
  const paths = [];
654
+ const seen = new Set(); // Track files to avoid duplicates (both .json and .json.gz)
567
655
  try {
568
656
  const entries = await fs.promises.readdir(fullPath, { withFileTypes: true });
569
657
  for (const entry of entries) {
570
- if (entry.isFile() && entry.name.endsWith('.json')) {
571
- paths.push(path.join(prefix, entry.name));
658
+ if (entry.isFile()) {
659
+ // Handle both .json and .json.gz files
660
+ if (entry.name.endsWith('.json.gz')) {
661
+ // Strip .gz extension and add the .json path
662
+ const normalizedName = entry.name.slice(0, -3); // Remove .gz
663
+ const normalizedPath = path.join(prefix, normalizedName);
664
+ if (!seen.has(normalizedPath)) {
665
+ paths.push(normalizedPath);
666
+ seen.add(normalizedPath);
667
+ }
668
+ }
669
+ else if (entry.name.endsWith('.json')) {
670
+ const filePath = path.join(prefix, entry.name);
671
+ if (!seen.has(filePath)) {
672
+ paths.push(filePath);
673
+ seen.add(filePath);
674
+ }
675
+ }
572
676
  }
573
677
  else if (entry.isDirectory()) {
574
678
  const subdirPaths = await this.listObjectsUnderPath(path.join(prefix, entry.name));
@@ -646,7 +750,7 @@ export class FileSystemStorage extends BaseStorage {
646
750
  }
647
751
  // Get page of files
648
752
  const pageFiles = nounFiles.slice(startIndex, startIndex + limit);
649
- // Load nouns - count actual successfully loaded items
753
+ // v4.0.0: Load nouns and combine with metadata
650
754
  const items = [];
651
755
  let successfullyLoaded = 0;
652
756
  let totalValidFiles = 0;
@@ -654,18 +758,21 @@ export class FileSystemStorage extends BaseStorage {
654
758
  totalValidFiles = this.totalNounCount;
655
759
  // No need to count files anymore - we maintain accurate counts
656
760
  // This eliminates the O(n) operation completely
657
- // Second pass: load the current page
761
+ // Second pass: load the current page with metadata
658
762
  for (const file of pageFiles) {
659
763
  try {
660
764
  const id = file.replace('.json', '');
661
765
  const data = await fs.promises.readFile(this.getNodePath(id), 'utf-8');
662
- const noun = JSON.parse(data);
766
+ const parsedNoun = JSON.parse(data);
767
+ // v4.0.0: Load metadata from separate storage
768
+ const metadata = await this.getNounMetadata(id);
769
+ if (!metadata)
770
+ continue;
663
771
  // Apply filter if provided
664
772
  if (options.filter) {
665
- // Simple filter implementation
666
773
  let matches = true;
667
774
  for (const [key, value] of Object.entries(options.filter)) {
668
- if (noun.metadata && noun.metadata[key] !== value) {
775
+ if (metadata[key] !== value) {
669
776
  matches = false;
670
777
  break;
671
778
  }
@@ -673,7 +780,24 @@ export class FileSystemStorage extends BaseStorage {
673
780
  if (!matches)
674
781
  continue;
675
782
  }
676
- items.push(noun);
783
+ // Convert connections if needed
784
+ let connections = parsedNoun.connections;
785
+ if (connections && typeof connections === 'object' && !(connections instanceof Map)) {
786
+ const connectionsMap = new Map();
787
+ for (const [level, nodeIds] of Object.entries(connections)) {
788
+ connectionsMap.set(Number(level), new Set(nodeIds));
789
+ }
790
+ connections = connectionsMap;
791
+ }
792
+ // v4.0.0: Create HNSWNounWithMetadata by combining noun with metadata
793
+ const nounWithMetadata = {
794
+ id: parsedNoun.id,
795
+ vector: parsedNoun.vector,
796
+ connections: connections,
797
+ level: parsedNoun.level || 0,
798
+ metadata: metadata
799
+ };
800
+ items.push(nounWithMetadata);
677
801
  successfullyLoaded++;
678
802
  }
679
803
  catch (error) {
@@ -895,21 +1019,17 @@ export class FileSystemStorage extends BaseStorage {
895
1019
  }
896
1020
  /**
897
1021
  * Get a noun from storage (internal implementation)
898
- * Combines vector data from getNode() with metadata from getNounMetadata()
1022
+ * v4.0.0: Returns ONLY vector data (no metadata field)
1023
+ * Base class combines with metadata via getNoun() -> HNSWNounWithMetadata
899
1024
  */
900
1025
  async getNoun_internal(id) {
901
- // Get vector data (lightweight)
1026
+ // v4.0.0: Return ONLY vector data (no metadata field)
902
1027
  const node = await this.getNode(id);
903
1028
  if (!node) {
904
1029
  return null;
905
1030
  }
906
- // Get metadata (entity data in 2-file system)
907
- const metadata = await this.getNounMetadata(id);
908
- // Combine into complete noun object
909
- return {
910
- ...node,
911
- metadata: metadata || {}
912
- };
1031
+ // Return pure vector structure
1032
+ return node;
913
1033
  }
914
1034
  /**
915
1035
  * Get nouns by noun type
@@ -931,21 +1051,17 @@ export class FileSystemStorage extends BaseStorage {
931
1051
  }
932
1052
  /**
933
1053
  * Get a verb from storage (internal implementation)
934
- * Combines vector data from getEdge() with metadata from getVerbMetadata()
1054
+ * v4.0.0: Returns ONLY vector + core relational fields (no metadata field)
1055
+ * Base class combines with metadata via getVerb() -> HNSWVerbWithMetadata
935
1056
  */
936
1057
  async getVerb_internal(id) {
937
- // Get vector data (lightweight)
1058
+ // v4.0.0: Return ONLY vector + core relational data (no metadata field)
938
1059
  const edge = await this.getEdge(id);
939
1060
  if (!edge) {
940
1061
  return null;
941
1062
  }
942
- // Get metadata (relationship data in 2-file system)
943
- const metadata = await this.getVerbMetadata(id);
944
- // Combine into complete verb object
945
- return {
946
- ...edge,
947
- metadata: metadata || {}
948
- };
1063
+ // Return pure vector + core fields structure
1064
+ return edge;
949
1065
  }
950
1066
  /**
951
1067
  * Get verbs by source
@@ -1029,22 +1145,9 @@ export class FileSystemStorage extends BaseStorage {
1029
1145
  const edge = JSON.parse(data);
1030
1146
  // Get metadata which contains the actual verb information
1031
1147
  const metadata = await this.getVerbMetadata(id);
1032
- // If no metadata exists, try to reconstruct basic metadata from filename
1148
+ // v4.0.0: No fallbacks - skip verbs without metadata
1033
1149
  if (!metadata) {
1034
- console.warn(`Verb ${id} has no metadata, trying to create minimal verb`);
1035
- // Create minimal GraphVerb without full metadata
1036
- const minimalVerb = {
1037
- id: edge.id,
1038
- vector: edge.vector,
1039
- connections: edge.connections || new Map(),
1040
- sourceId: 'unknown',
1041
- targetId: 'unknown',
1042
- source: 'unknown',
1043
- target: 'unknown',
1044
- type: 'relationship',
1045
- verb: 'relatedTo'
1046
- };
1047
- verbs.push(minimalVerb);
1150
+ console.warn(`Verb ${id} has no metadata, skipping`);
1048
1151
  continue;
1049
1152
  }
1050
1153
  // Convert connections Map to proper format if needed
@@ -1056,24 +1159,15 @@ export class FileSystemStorage extends BaseStorage {
1056
1159
  }
1057
1160
  connections = connectionsMap;
1058
1161
  }
1059
- // Properly reconstruct GraphVerb from HNSWVerb + metadata
1060
- const verb = {
1162
+ // v4.0.0: Clean HNSWVerbWithMetadata construction
1163
+ const verbWithMetadata = {
1061
1164
  id: edge.id,
1062
- vector: edge.vector, // Include the vector field!
1165
+ vector: edge.vector,
1063
1166
  connections: connections,
1064
- sourceId: metadata.sourceId || metadata.source,
1065
- targetId: metadata.targetId || metadata.target,
1066
- source: metadata.source || metadata.sourceId,
1067
- target: metadata.target || metadata.targetId,
1068
- verb: metadata.verb || metadata.type,
1069
- type: metadata.type || metadata.verb,
1070
- weight: metadata.weight,
1071
- metadata: metadata.metadata || metadata,
1072
- data: metadata.data,
1073
- createdAt: metadata.createdAt,
1074
- updatedAt: metadata.updatedAt,
1075
- createdBy: metadata.createdBy,
1076
- embedding: metadata.embedding || edge.vector
1167
+ verb: edge.verb,
1168
+ sourceId: edge.sourceId,
1169
+ targetId: edge.targetId,
1170
+ metadata: metadata
1077
1171
  };
1078
1172
  // Apply filters if provided
1079
1173
  if (options.filter) {
@@ -1081,22 +1175,19 @@ export class FileSystemStorage extends BaseStorage {
1081
1175
  // Check verbType filter
1082
1176
  if (filter.verbType) {
1083
1177
  const types = Array.isArray(filter.verbType) ? filter.verbType : [filter.verbType];
1084
- const verbType = verb.type || verb.verb;
1085
- if (verbType && !types.includes(verbType))
1178
+ if (!types.includes(verbWithMetadata.verb))
1086
1179
  continue;
1087
1180
  }
1088
1181
  // Check sourceId filter
1089
1182
  if (filter.sourceId) {
1090
1183
  const sources = Array.isArray(filter.sourceId) ? filter.sourceId : [filter.sourceId];
1091
- const sourceId = verb.sourceId || verb.source;
1092
- if (!sourceId || !sources.includes(sourceId))
1184
+ if (!sources.includes(verbWithMetadata.sourceId))
1093
1185
  continue;
1094
1186
  }
1095
1187
  // Check targetId filter
1096
1188
  if (filter.targetId) {
1097
1189
  const targets = Array.isArray(filter.targetId) ? filter.targetId : [filter.targetId];
1098
- const targetId = verb.targetId || verb.target;
1099
- if (!targetId || !targets.includes(targetId))
1190
+ if (!targets.includes(verbWithMetadata.targetId))
1100
1191
  continue;
1101
1192
  }
1102
1193
  // Check service filter
@@ -1106,7 +1197,7 @@ export class FileSystemStorage extends BaseStorage {
1106
1197
  continue;
1107
1198
  }
1108
1199
  }
1109
- verbs.push(verb);
1200
+ verbs.push(verbWithMetadata);
1110
1201
  successfullyLoaded++;
1111
1202
  }
1112
1203
  catch (error) {
@@ -1485,33 +1576,21 @@ export class FileSystemStorage extends BaseStorage {
1485
1576
  const validVerbFiles = await this.getAllFilesAtDepth(this.verbsDir, depthToUse);
1486
1577
  this.totalVerbCount = validVerbFiles.length;
1487
1578
  // Sample some files to get type distribution (don't read all)
1579
+ // v4.0.0: Load metadata separately for type information
1488
1580
  const sampleSize = Math.min(100, validNounFiles.length);
1489
1581
  for (let i = 0; i < sampleSize; i++) {
1490
1582
  try {
1491
1583
  const file = validNounFiles[i];
1492
1584
  const id = file.replace('.json', '');
1493
- // Construct path using detected depth (not cached depth which may be wrong)
1494
- let filePath;
1495
- switch (depthToUse) {
1496
- case 0:
1497
- filePath = path.join(this.nounsDir, `${id}.json`);
1498
- break;
1499
- case 1:
1500
- filePath = path.join(this.nounsDir, id.substring(0, 2), `${id}.json`);
1501
- break;
1502
- case 2:
1503
- filePath = path.join(this.nounsDir, id.substring(0, 2), id.substring(2, 4), `${id}.json`);
1504
- break;
1505
- default:
1506
- throw new Error(`Unsupported depth: ${depthToUse}`);
1585
+ // v4.0.0: Load metadata from separate storage for type info
1586
+ const metadata = await this.getNounMetadata(id);
1587
+ if (metadata) {
1588
+ const type = metadata.noun || 'default';
1589
+ this.entityCounts.set(type, (this.entityCounts.get(type) || 0) + 1);
1507
1590
  }
1508
- const data = await fs.promises.readFile(filePath, 'utf-8');
1509
- const noun = JSON.parse(data);
1510
- const type = noun.metadata?.type || noun.metadata?.nounType || 'default';
1511
- this.entityCounts.set(type, (this.entityCounts.get(type) || 0) + 1);
1512
1591
  }
1513
1592
  catch {
1514
- // Skip invalid files
1593
+ // Skip invalid files or missing metadata
1515
1594
  }
1516
1595
  }
1517
1596
  // Extrapolate counts if we sampled
@@ -2044,52 +2123,50 @@ export class FileSystemStorage extends BaseStorage {
2044
2123
  const data = await fs.promises.readFile(filePath, 'utf-8');
2045
2124
  const edge = JSON.parse(data);
2046
2125
  const metadata = await this.getVerbMetadata(id);
2126
+ // v4.0.0: No fallbacks - skip verbs without metadata
2047
2127
  if (!metadata) {
2048
2128
  processedCount++;
2049
2129
  return true; // continue, skip this verb
2050
2130
  }
2051
- // Reconstruct GraphVerb
2052
- const verb = {
2131
+ // Convert connections if needed
2132
+ let connections = edge.connections;
2133
+ if (connections && typeof connections === 'object' && !(connections instanceof Map)) {
2134
+ const connectionsMap = new Map();
2135
+ for (const [level, nodeIds] of Object.entries(connections)) {
2136
+ connectionsMap.set(Number(level), new Set(nodeIds));
2137
+ }
2138
+ connections = connectionsMap;
2139
+ }
2140
+ // v4.0.0: Clean HNSWVerbWithMetadata construction
2141
+ const verbWithMetadata = {
2053
2142
  id: edge.id,
2054
2143
  vector: edge.vector,
2055
- connections: edge.connections || new Map(),
2056
- sourceId: metadata.sourceId || metadata.source,
2057
- targetId: metadata.targetId || metadata.target,
2058
- source: metadata.source || metadata.sourceId,
2059
- target: metadata.target || metadata.targetId,
2060
- verb: metadata.verb || metadata.type,
2061
- type: metadata.type || metadata.verb,
2062
- weight: metadata.weight,
2063
- metadata: metadata.metadata || metadata,
2064
- data: metadata.data,
2065
- createdAt: metadata.createdAt,
2066
- updatedAt: metadata.updatedAt,
2067
- createdBy: metadata.createdBy,
2068
- embedding: metadata.embedding || edge.vector
2144
+ connections: connections || new Map(),
2145
+ verb: edge.verb,
2146
+ sourceId: edge.sourceId,
2147
+ targetId: edge.targetId,
2148
+ metadata: metadata
2069
2149
  };
2070
2150
  // Apply filters
2071
2151
  if (options.filter) {
2072
2152
  const filter = options.filter;
2073
2153
  if (filter.verbType) {
2074
2154
  const types = Array.isArray(filter.verbType) ? filter.verbType : [filter.verbType];
2075
- const verbType = verb.type || verb.verb;
2076
- if (verbType && !types.includes(verbType))
2155
+ if (!types.includes(verbWithMetadata.verb))
2077
2156
  return true; // continue
2078
2157
  }
2079
2158
  if (filter.sourceId) {
2080
2159
  const sources = Array.isArray(filter.sourceId) ? filter.sourceId : [filter.sourceId];
2081
- const sourceId = verb.sourceId || verb.source;
2082
- if (!sourceId || !sources.includes(sourceId))
2160
+ if (!sources.includes(verbWithMetadata.sourceId))
2083
2161
  return true; // continue
2084
2162
  }
2085
2163
  if (filter.targetId) {
2086
2164
  const targets = Array.isArray(filter.targetId) ? filter.targetId : [filter.targetId];
2087
- const targetId = verb.targetId || verb.target;
2088
- if (!targetId || !targets.includes(targetId))
2165
+ if (!targets.includes(verbWithMetadata.targetId))
2089
2166
  return true; // continue
2090
2167
  }
2091
2168
  }
2092
- verbs.push(verb);
2169
+ verbs.push(verbWithMetadata);
2093
2170
  resultCount++;
2094
2171
  processedCount++;
2095
2172
  return true; // continue