@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.
- package/CHANGELOG.md +242 -0
- package/README.md +358 -658
- package/dist/api/ConfigAPI.js +56 -19
- package/dist/api/DataAPI.js +24 -18
- package/dist/augmentations/storageAugmentations.d.ts +24 -0
- package/dist/augmentations/storageAugmentations.js +22 -0
- package/dist/brainy.js +32 -9
- package/dist/cli/commands/core.d.ts +20 -10
- package/dist/cli/commands/core.js +384 -82
- package/dist/cli/commands/import.d.ts +41 -0
- package/dist/cli/commands/import.js +456 -0
- package/dist/cli/commands/insights.d.ts +34 -0
- package/dist/cli/commands/insights.js +300 -0
- package/dist/cli/commands/neural.d.ts +6 -12
- package/dist/cli/commands/neural.js +113 -10
- package/dist/cli/commands/nlp.d.ts +28 -0
- package/dist/cli/commands/nlp.js +246 -0
- package/dist/cli/commands/storage.d.ts +64 -0
- package/dist/cli/commands/storage.js +730 -0
- package/dist/cli/index.js +210 -24
- package/dist/coreTypes.d.ts +206 -34
- package/dist/distributed/configManager.js +8 -6
- package/dist/distributed/shardMigration.js +2 -0
- package/dist/distributed/storageDiscovery.js +6 -4
- package/dist/embeddings/EmbeddingManager.d.ts +2 -2
- package/dist/embeddings/EmbeddingManager.js +5 -1
- package/dist/graph/lsm/LSMTree.js +32 -20
- package/dist/hnsw/typeAwareHNSWIndex.js +6 -2
- package/dist/storage/adapters/azureBlobStorage.d.ts +545 -0
- package/dist/storage/adapters/azureBlobStorage.js +1809 -0
- package/dist/storage/adapters/baseStorageAdapter.d.ts +16 -13
- package/dist/storage/adapters/fileSystemStorage.d.ts +21 -9
- package/dist/storage/adapters/fileSystemStorage.js +204 -127
- package/dist/storage/adapters/gcsStorage.d.ts +119 -9
- package/dist/storage/adapters/gcsStorage.js +317 -62
- package/dist/storage/adapters/memoryStorage.d.ts +30 -18
- package/dist/storage/adapters/memoryStorage.js +99 -94
- package/dist/storage/adapters/opfsStorage.d.ts +48 -10
- package/dist/storage/adapters/opfsStorage.js +201 -80
- package/dist/storage/adapters/r2Storage.d.ts +12 -5
- package/dist/storage/adapters/r2Storage.js +63 -15
- package/dist/storage/adapters/s3CompatibleStorage.d.ts +164 -17
- package/dist/storage/adapters/s3CompatibleStorage.js +472 -80
- package/dist/storage/adapters/typeAwareStorageAdapter.d.ts +38 -6
- package/dist/storage/adapters/typeAwareStorageAdapter.js +218 -39
- package/dist/storage/baseStorage.d.ts +41 -38
- package/dist/storage/baseStorage.js +110 -134
- package/dist/storage/storageFactory.d.ts +29 -2
- package/dist/storage/storageFactory.js +30 -1
- package/dist/utils/entityIdMapper.js +5 -2
- package/dist/utils/fieldTypeInference.js +8 -1
- package/dist/utils/metadataFilter.d.ts +3 -2
- package/dist/utils/metadataFilter.js +1 -0
- package/dist/utils/metadataIndex.d.ts +2 -1
- package/dist/utils/metadataIndex.js +9 -1
- package/dist/utils/metadataIndexChunking.js +9 -4
- package/dist/utils/periodicCleanup.js +1 -0
- 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
|
-
|
|
17
|
-
|
|
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 (
|
|
197
|
+
// Update counts for new nodes (v4.0.0: load metadata separately)
|
|
184
198
|
if (isNew) {
|
|
185
|
-
|
|
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
|
|
327
|
+
// Load metadata to get type for count update (v4.0.0: separate storage)
|
|
312
328
|
try {
|
|
313
|
-
const
|
|
314
|
-
if (
|
|
315
|
-
const type =
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
393
|
-
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
|
-
//
|
|
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
|
-
//
|
|
436
|
-
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
|
-
|
|
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
|
|
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()
|
|
571
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
907
|
-
|
|
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
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
943
|
-
|
|
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
|
-
//
|
|
1148
|
+
// v4.0.0: No fallbacks - skip verbs without metadata
|
|
1033
1149
|
if (!metadata) {
|
|
1034
|
-
console.warn(`Verb ${id} has no metadata,
|
|
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
|
-
//
|
|
1060
|
-
const
|
|
1162
|
+
// v4.0.0: Clean HNSWVerbWithMetadata construction
|
|
1163
|
+
const verbWithMetadata = {
|
|
1061
1164
|
id: edge.id,
|
|
1062
|
-
vector: edge.vector,
|
|
1165
|
+
vector: edge.vector,
|
|
1063
1166
|
connections: connections,
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
//
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
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
|
-
//
|
|
2052
|
-
|
|
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:
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2088
|
-
if (!targetId || !targets.includes(targetId))
|
|
2165
|
+
if (!targets.includes(verbWithMetadata.targetId))
|
|
2089
2166
|
return true; // continue
|
|
2090
2167
|
}
|
|
2091
2168
|
}
|
|
2092
|
-
verbs.push(
|
|
2169
|
+
verbs.push(verbWithMetadata);
|
|
2093
2170
|
resultCount++;
|
|
2094
2171
|
processedCount++;
|
|
2095
2172
|
return true; // continue
|