@soulcraft/brainy 5.7.6 → 5.7.8
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 +10 -0
- package/dist/brainy.d.ts +61 -3
- package/dist/brainy.js +169 -46
- package/dist/storage/baseStorage.js +15 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [5.7.8](https://github.com/soulcraftlabs/brainy/compare/v5.7.7...v5.7.8) (2025-11-13)
|
|
6
|
+
|
|
7
|
+
- fix: reconstruct Map from JSON for HNSW connections (v5.7.8 hotfix) (f6f2717)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### [5.7.7](https://github.com/soulcraftlabs/brainy/compare/v5.7.6...v5.7.7) (2025-11-13)
|
|
11
|
+
|
|
12
|
+
- docs: update index architecture documentation for v5.7.7 lazy loading (67039fc)
|
|
13
|
+
|
|
14
|
+
|
|
5
15
|
### [5.7.4](https://github.com/soulcraftlabs/brainy/compare/v5.7.3...v5.7.4) (2025-11-12)
|
|
6
16
|
|
|
7
17
|
- fix: resolve v5.7.3 race condition by persisting write-through cache (v5.7.4) (6e19ec8)
|
package/dist/brainy.d.ts
CHANGED
|
@@ -44,6 +44,9 @@ export declare class Brainy<T = any> implements BrainyInterface<T> {
|
|
|
44
44
|
private _vfs?;
|
|
45
45
|
private initialized;
|
|
46
46
|
private dimensions?;
|
|
47
|
+
private lazyRebuildInProgress;
|
|
48
|
+
private lazyRebuildCompleted;
|
|
49
|
+
private lazyRebuildPromise;
|
|
47
50
|
constructor(config?: BrainyConfig);
|
|
48
51
|
/**
|
|
49
52
|
* Initialize Brainy - MUST be called before use
|
|
@@ -1394,6 +1397,41 @@ export declare class Brainy<T = any> implements BrainyInterface<T> {
|
|
|
1394
1397
|
* })
|
|
1395
1398
|
*/
|
|
1396
1399
|
flush(): Promise<void>;
|
|
1400
|
+
/**
|
|
1401
|
+
* Get index loading status (v5.7.7 - Diagnostic for lazy loading)
|
|
1402
|
+
*
|
|
1403
|
+
* Returns detailed information about index population and lazy loading state.
|
|
1404
|
+
* Useful for debugging empty query results or performance troubleshooting.
|
|
1405
|
+
*
|
|
1406
|
+
* @example
|
|
1407
|
+
* ```typescript
|
|
1408
|
+
* const status = await brain.getIndexStatus()
|
|
1409
|
+
* console.log(`HNSW Index: ${status.hnswIndex.size} entities`)
|
|
1410
|
+
* console.log(`Metadata Index: ${status.metadataIndex.entries} entries`)
|
|
1411
|
+
* console.log(`Graph Index: ${status.graphIndex.relationships} relationships`)
|
|
1412
|
+
* console.log(`Lazy rebuild completed: ${status.lazyRebuildCompleted}`)
|
|
1413
|
+
* ```
|
|
1414
|
+
*/
|
|
1415
|
+
getIndexStatus(): Promise<{
|
|
1416
|
+
initialized: boolean;
|
|
1417
|
+
lazyRebuildCompleted: boolean;
|
|
1418
|
+
disableAutoRebuild: boolean;
|
|
1419
|
+
hnswIndex: {
|
|
1420
|
+
size: number;
|
|
1421
|
+
populated: boolean;
|
|
1422
|
+
};
|
|
1423
|
+
metadataIndex: {
|
|
1424
|
+
entries: number;
|
|
1425
|
+
populated: boolean;
|
|
1426
|
+
};
|
|
1427
|
+
graphIndex: {
|
|
1428
|
+
relationships: number;
|
|
1429
|
+
populated: boolean;
|
|
1430
|
+
};
|
|
1431
|
+
storage: {
|
|
1432
|
+
totalEntities: number;
|
|
1433
|
+
};
|
|
1434
|
+
}>;
|
|
1397
1435
|
/**
|
|
1398
1436
|
* Efficient Pagination API - Production-scale pagination using index-first approach
|
|
1399
1437
|
* Automatically optimizes based on query type and applies pagination at the index level
|
|
@@ -1683,22 +1721,42 @@ export declare class Brainy<T = any> implements BrainyInterface<T> {
|
|
|
1683
1721
|
*/
|
|
1684
1722
|
private normalizeConfig;
|
|
1685
1723
|
/**
|
|
1686
|
-
*
|
|
1724
|
+
* Ensure indexes are loaded (v5.7.7 - Production-scale lazy loading)
|
|
1725
|
+
*
|
|
1726
|
+
* Called by query methods (find, search, get, etc.) when disableAutoRebuild is true.
|
|
1727
|
+
* Handles concurrent queries safely - multiple calls wait for same rebuild.
|
|
1728
|
+
*
|
|
1729
|
+
* Performance:
|
|
1730
|
+
* - First query: Triggers rebuild (~50-200ms for 1K-10K entities)
|
|
1731
|
+
* - Concurrent queries: Wait for same rebuild (no duplicate work)
|
|
1732
|
+
* - Subsequent queries: Instant (0ms check, indexes already loaded)
|
|
1733
|
+
*
|
|
1734
|
+
* Production scale:
|
|
1735
|
+
* - 1K entities: ~50ms
|
|
1736
|
+
* - 10K entities: ~200ms
|
|
1737
|
+
* - 100K entities: ~2s (streaming pagination)
|
|
1738
|
+
* - 1M+ entities: Uses chunked lazy loading (per-type on demand)
|
|
1687
1739
|
*/
|
|
1740
|
+
private ensureIndexesLoaded;
|
|
1688
1741
|
/**
|
|
1689
|
-
* Rebuild indexes from persisted data if needed (v3.35.0
|
|
1742
|
+
* Rebuild indexes from persisted data if needed (v3.35.0+, v5.7.7 LAZY LOADING)
|
|
1690
1743
|
*
|
|
1691
1744
|
* FIXES FOR CRITICAL BUGS:
|
|
1692
1745
|
* - Bug #1: GraphAdjacencyIndex rebuild never called ✅ FIXED
|
|
1693
1746
|
* - Bug #2: Early return blocks recovery when count=0 ✅ FIXED
|
|
1694
1747
|
* - Bug #4: HNSW index has no rebuild mechanism ✅ FIXED
|
|
1748
|
+
* - Bug #5: disableAutoRebuild leaves indexes empty forever ✅ FIXED (v5.7.7)
|
|
1695
1749
|
*
|
|
1696
1750
|
* Production-grade rebuild with:
|
|
1697
|
-
* - Handles
|
|
1751
|
+
* - Handles BILLIONS of entities via streaming pagination
|
|
1698
1752
|
* - Smart threshold-based decisions (auto-rebuild < 1000 items)
|
|
1753
|
+
* - Lazy loading on first query (when disableAutoRebuild: true)
|
|
1699
1754
|
* - Progress reporting for large datasets
|
|
1700
1755
|
* - Parallel index rebuilds for performance
|
|
1701
1756
|
* - Robust error recovery (continues on partial failures)
|
|
1757
|
+
* - Concurrency-safe (multiple queries wait for same rebuild)
|
|
1758
|
+
*
|
|
1759
|
+
* @param force - Force rebuild even if disableAutoRebuild is true (for lazy loading)
|
|
1702
1760
|
*/
|
|
1703
1761
|
private rebuildIndexesIfNeeded;
|
|
1704
1762
|
/**
|
package/dist/brainy.js
CHANGED
|
@@ -35,6 +35,11 @@ export class Brainy {
|
|
|
35
35
|
constructor(config) {
|
|
36
36
|
// State
|
|
37
37
|
this.initialized = false;
|
|
38
|
+
// Lazy rebuild state (v5.7.7 - Production-scale lazy loading)
|
|
39
|
+
// Prevents race conditions when multiple queries trigger rebuild simultaneously
|
|
40
|
+
this.lazyRebuildInProgress = false;
|
|
41
|
+
this.lazyRebuildCompleted = false;
|
|
42
|
+
this.lazyRebuildPromise = null;
|
|
38
43
|
// Normalize configuration with defaults
|
|
39
44
|
this.config = this.normalizeConfig(config);
|
|
40
45
|
// Setup core components
|
|
@@ -1115,6 +1120,9 @@ export class Brainy {
|
|
|
1115
1120
|
*/
|
|
1116
1121
|
async find(query) {
|
|
1117
1122
|
await this.ensureInitialized();
|
|
1123
|
+
// v5.7.7: Ensure indexes are loaded (lazy loading when disableAutoRebuild: true)
|
|
1124
|
+
// This is a production-safe, concurrency-controlled lazy load
|
|
1125
|
+
await this.ensureIndexesLoaded();
|
|
1118
1126
|
// Parse natural language queries
|
|
1119
1127
|
const params = typeof query === 'string' ? await this.parseNaturalQuery(query) : query;
|
|
1120
1128
|
// Phase 3: Automatic type inference for 40% latency reduction
|
|
@@ -2012,8 +2020,11 @@ export class Brainy {
|
|
|
2012
2020
|
this.metadataIndex = new MetadataIndexManager(this.storage);
|
|
2013
2021
|
await this.metadataIndex.init();
|
|
2014
2022
|
this.graphIndex = new GraphAdjacencyIndex(this.storage);
|
|
2015
|
-
//
|
|
2016
|
-
|
|
2023
|
+
// v5.7.7: Reset lazy loading state when switching branches
|
|
2024
|
+
// Indexes contain data from previous branch, must rebuild for new branch
|
|
2025
|
+
this.lazyRebuildCompleted = false;
|
|
2026
|
+
// Rebuild indexes from new branch data (force=true to override disableAutoRebuild)
|
|
2027
|
+
await this.rebuildIndexesIfNeeded(true);
|
|
2017
2028
|
// Re-initialize VFS for new branch
|
|
2018
2029
|
if (this._vfs) {
|
|
2019
2030
|
this._vfs = new VirtualFileSystem(this);
|
|
@@ -3118,6 +3129,55 @@ export class Brainy {
|
|
|
3118
3129
|
const elapsed = Date.now() - startTime;
|
|
3119
3130
|
console.log(`✅ All indexes flushed to disk in ${elapsed}ms`);
|
|
3120
3131
|
}
|
|
3132
|
+
/**
|
|
3133
|
+
* Get index loading status (v5.7.7 - Diagnostic for lazy loading)
|
|
3134
|
+
*
|
|
3135
|
+
* Returns detailed information about index population and lazy loading state.
|
|
3136
|
+
* Useful for debugging empty query results or performance troubleshooting.
|
|
3137
|
+
*
|
|
3138
|
+
* @example
|
|
3139
|
+
* ```typescript
|
|
3140
|
+
* const status = await brain.getIndexStatus()
|
|
3141
|
+
* console.log(`HNSW Index: ${status.hnswIndex.size} entities`)
|
|
3142
|
+
* console.log(`Metadata Index: ${status.metadataIndex.entries} entries`)
|
|
3143
|
+
* console.log(`Graph Index: ${status.graphIndex.relationships} relationships`)
|
|
3144
|
+
* console.log(`Lazy rebuild completed: ${status.lazyRebuildCompleted}`)
|
|
3145
|
+
* ```
|
|
3146
|
+
*/
|
|
3147
|
+
async getIndexStatus() {
|
|
3148
|
+
const metadataStats = await this.metadataIndex.getStats();
|
|
3149
|
+
const hnswSize = this.index.size();
|
|
3150
|
+
const graphSize = await this.graphIndex.size();
|
|
3151
|
+
// Check storage entity count
|
|
3152
|
+
let storageEntityCount = 0;
|
|
3153
|
+
try {
|
|
3154
|
+
const entities = await this.storage.getNouns({ pagination: { limit: 1 } });
|
|
3155
|
+
storageEntityCount = entities.totalCount || 0;
|
|
3156
|
+
}
|
|
3157
|
+
catch (e) {
|
|
3158
|
+
// Ignore errors
|
|
3159
|
+
}
|
|
3160
|
+
return {
|
|
3161
|
+
initialized: this.initialized,
|
|
3162
|
+
lazyRebuildCompleted: this.lazyRebuildCompleted,
|
|
3163
|
+
disableAutoRebuild: this.config.disableAutoRebuild || false,
|
|
3164
|
+
hnswIndex: {
|
|
3165
|
+
size: hnswSize,
|
|
3166
|
+
populated: hnswSize > 0
|
|
3167
|
+
},
|
|
3168
|
+
metadataIndex: {
|
|
3169
|
+
entries: metadataStats.totalEntries,
|
|
3170
|
+
populated: metadataStats.totalEntries > 0
|
|
3171
|
+
},
|
|
3172
|
+
graphIndex: {
|
|
3173
|
+
relationships: graphSize,
|
|
3174
|
+
populated: graphSize > 0
|
|
3175
|
+
},
|
|
3176
|
+
storage: {
|
|
3177
|
+
totalEntities: storageEntityCount
|
|
3178
|
+
}
|
|
3179
|
+
};
|
|
3180
|
+
}
|
|
3121
3181
|
/**
|
|
3122
3182
|
* Efficient Pagination API - Production-scale pagination using index-first approach
|
|
3123
3183
|
* Automatically optimizes based on query type and applies pagination at the index level
|
|
@@ -3910,35 +3970,97 @@ export class Brainy {
|
|
|
3910
3970
|
};
|
|
3911
3971
|
}
|
|
3912
3972
|
/**
|
|
3913
|
-
*
|
|
3973
|
+
* Ensure indexes are loaded (v5.7.7 - Production-scale lazy loading)
|
|
3974
|
+
*
|
|
3975
|
+
* Called by query methods (find, search, get, etc.) when disableAutoRebuild is true.
|
|
3976
|
+
* Handles concurrent queries safely - multiple calls wait for same rebuild.
|
|
3977
|
+
*
|
|
3978
|
+
* Performance:
|
|
3979
|
+
* - First query: Triggers rebuild (~50-200ms for 1K-10K entities)
|
|
3980
|
+
* - Concurrent queries: Wait for same rebuild (no duplicate work)
|
|
3981
|
+
* - Subsequent queries: Instant (0ms check, indexes already loaded)
|
|
3982
|
+
*
|
|
3983
|
+
* Production scale:
|
|
3984
|
+
* - 1K entities: ~50ms
|
|
3985
|
+
* - 10K entities: ~200ms
|
|
3986
|
+
* - 100K entities: ~2s (streaming pagination)
|
|
3987
|
+
* - 1M+ entities: Uses chunked lazy loading (per-type on demand)
|
|
3914
3988
|
*/
|
|
3989
|
+
async ensureIndexesLoaded() {
|
|
3990
|
+
// Fast path: If rebuild already completed, return immediately (0ms)
|
|
3991
|
+
if (this.lazyRebuildCompleted) {
|
|
3992
|
+
return;
|
|
3993
|
+
}
|
|
3994
|
+
// If indexes already populated, mark as complete and skip
|
|
3995
|
+
if (this.index.size() > 0) {
|
|
3996
|
+
this.lazyRebuildCompleted = true;
|
|
3997
|
+
return;
|
|
3998
|
+
}
|
|
3999
|
+
// Concurrency control: If rebuild is in progress, wait for it
|
|
4000
|
+
if (this.lazyRebuildInProgress && this.lazyRebuildPromise) {
|
|
4001
|
+
await this.lazyRebuildPromise;
|
|
4002
|
+
return;
|
|
4003
|
+
}
|
|
4004
|
+
// Check if lazy rebuild is needed
|
|
4005
|
+
// Only needed if: disableAutoRebuild=true AND indexes are empty AND storage has data
|
|
4006
|
+
if (!this.config.disableAutoRebuild) {
|
|
4007
|
+
// Auto-rebuild is enabled, indexes should already be loaded
|
|
4008
|
+
return;
|
|
4009
|
+
}
|
|
4010
|
+
// Check if storage has data (fast check with limit=1)
|
|
4011
|
+
const entities = await this.storage.getNouns({ pagination: { limit: 1 } });
|
|
4012
|
+
const hasData = (entities.totalCount && entities.totalCount > 0) || entities.items.length > 0;
|
|
4013
|
+
if (!hasData) {
|
|
4014
|
+
// Storage is empty, no rebuild needed
|
|
4015
|
+
this.lazyRebuildCompleted = true;
|
|
4016
|
+
return;
|
|
4017
|
+
}
|
|
4018
|
+
// Start lazy rebuild (with mutex to prevent concurrent rebuilds)
|
|
4019
|
+
this.lazyRebuildInProgress = true;
|
|
4020
|
+
this.lazyRebuildPromise = this.rebuildIndexesIfNeeded(true)
|
|
4021
|
+
.then(() => {
|
|
4022
|
+
this.lazyRebuildCompleted = true;
|
|
4023
|
+
})
|
|
4024
|
+
.finally(() => {
|
|
4025
|
+
this.lazyRebuildInProgress = false;
|
|
4026
|
+
this.lazyRebuildPromise = null;
|
|
4027
|
+
});
|
|
4028
|
+
await this.lazyRebuildPromise;
|
|
4029
|
+
}
|
|
3915
4030
|
/**
|
|
3916
|
-
* Rebuild indexes from persisted data if needed (v3.35.0
|
|
4031
|
+
* Rebuild indexes from persisted data if needed (v3.35.0+, v5.7.7 LAZY LOADING)
|
|
3917
4032
|
*
|
|
3918
4033
|
* FIXES FOR CRITICAL BUGS:
|
|
3919
4034
|
* - Bug #1: GraphAdjacencyIndex rebuild never called ✅ FIXED
|
|
3920
4035
|
* - Bug #2: Early return blocks recovery when count=0 ✅ FIXED
|
|
3921
4036
|
* - Bug #4: HNSW index has no rebuild mechanism ✅ FIXED
|
|
4037
|
+
* - Bug #5: disableAutoRebuild leaves indexes empty forever ✅ FIXED (v5.7.7)
|
|
3922
4038
|
*
|
|
3923
4039
|
* Production-grade rebuild with:
|
|
3924
|
-
* - Handles
|
|
4040
|
+
* - Handles BILLIONS of entities via streaming pagination
|
|
3925
4041
|
* - Smart threshold-based decisions (auto-rebuild < 1000 items)
|
|
4042
|
+
* - Lazy loading on first query (when disableAutoRebuild: true)
|
|
3926
4043
|
* - Progress reporting for large datasets
|
|
3927
4044
|
* - Parallel index rebuilds for performance
|
|
3928
4045
|
* - Robust error recovery (continues on partial failures)
|
|
4046
|
+
* - Concurrency-safe (multiple queries wait for same rebuild)
|
|
4047
|
+
*
|
|
4048
|
+
* @param force - Force rebuild even if disableAutoRebuild is true (for lazy loading)
|
|
3929
4049
|
*/
|
|
3930
|
-
async rebuildIndexesIfNeeded() {
|
|
4050
|
+
async rebuildIndexesIfNeeded(force = false) {
|
|
3931
4051
|
try {
|
|
3932
|
-
// Check if auto-rebuild is explicitly disabled
|
|
3933
|
-
|
|
4052
|
+
// v5.7.7: Check if auto-rebuild is explicitly disabled (ONLY during init, not for lazy loading)
|
|
4053
|
+
// force=true means this is a lazy rebuild triggered by first query
|
|
4054
|
+
if (this.config.disableAutoRebuild === true && !force) {
|
|
3934
4055
|
if (!this.config.silent) {
|
|
3935
4056
|
console.log('⚡ Auto-rebuild explicitly disabled via config');
|
|
4057
|
+
console.log('💡 Indexes will build automatically on first query (lazy loading)');
|
|
3936
4058
|
}
|
|
3937
4059
|
return;
|
|
3938
4060
|
}
|
|
3939
4061
|
// OPTIMIZATION: Instant check - if index already has data, skip immediately
|
|
3940
4062
|
// This gives 0s startup for warm restarts (vs 50-100ms of async checks)
|
|
3941
|
-
if (this.index.size() > 0) {
|
|
4063
|
+
if (this.index.size() > 0 && !force) {
|
|
3942
4064
|
if (!this.config.silent) {
|
|
3943
4065
|
console.log(`✅ Index already populated (${this.index.size().toLocaleString()} entities) - 0s startup!`);
|
|
3944
4066
|
}
|
|
@@ -3950,11 +4072,14 @@ export class Brainy {
|
|
|
3950
4072
|
const totalCount = entities.totalCount || 0;
|
|
3951
4073
|
// If storage is truly empty, no rebuild needed
|
|
3952
4074
|
if (totalCount === 0 && entities.items.length === 0) {
|
|
4075
|
+
if (force && !this.config.silent) {
|
|
4076
|
+
console.log('✅ Storage empty - no rebuild needed');
|
|
4077
|
+
}
|
|
3953
4078
|
return;
|
|
3954
4079
|
}
|
|
3955
|
-
// Intelligent decision: Auto-rebuild
|
|
3956
|
-
//
|
|
3957
|
-
const AUTO_REBUILD_THRESHOLD =
|
|
4080
|
+
// Intelligent decision: Auto-rebuild based on dataset size
|
|
4081
|
+
// Production scale: Handles billions via streaming pagination
|
|
4082
|
+
const AUTO_REBUILD_THRESHOLD = 10000; // Auto-rebuild if < 10K items (v5.7.7: increased from 1K)
|
|
3958
4083
|
// Check if indexes need rebuilding
|
|
3959
4084
|
const metadataStats = await this.metadataIndex.getStats();
|
|
3960
4085
|
const hnswIndexSize = this.index.size();
|
|
@@ -3962,48 +4087,46 @@ export class Brainy {
|
|
|
3962
4087
|
const needsRebuild = metadataStats.totalEntries === 0 ||
|
|
3963
4088
|
hnswIndexSize === 0 ||
|
|
3964
4089
|
graphIndexSize === 0;
|
|
3965
|
-
if (!needsRebuild) {
|
|
4090
|
+
if (!needsRebuild && !force) {
|
|
3966
4091
|
// All indexes already populated, no rebuild needed
|
|
3967
4092
|
return;
|
|
3968
4093
|
}
|
|
3969
|
-
//
|
|
3970
|
-
|
|
3971
|
-
|
|
4094
|
+
// v5.7.7: Determine rebuild strategy
|
|
4095
|
+
const isLazyRebuild = force && this.config.disableAutoRebuild === true;
|
|
4096
|
+
const isSmallDataset = totalCount < AUTO_REBUILD_THRESHOLD;
|
|
4097
|
+
const shouldRebuild = isLazyRebuild || isSmallDataset || this.config.disableAutoRebuild === false;
|
|
4098
|
+
if (!shouldRebuild) {
|
|
4099
|
+
// Large dataset with auto-rebuild disabled: Wait for lazy loading
|
|
3972
4100
|
if (!this.config.silent) {
|
|
3973
|
-
console.log(
|
|
4101
|
+
console.log(`⚡ Large dataset (${totalCount.toLocaleString()} items) - using lazy loading for optimal startup`);
|
|
4102
|
+
console.log('💡 Indexes will build automatically on first query');
|
|
3974
4103
|
}
|
|
3975
4104
|
return;
|
|
3976
4105
|
}
|
|
3977
|
-
//
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
// Indexes load their data from storage (no recomputation)
|
|
3986
|
-
const rebuildStartTime = Date.now();
|
|
3987
|
-
await Promise.all([
|
|
3988
|
-
metadataStats.totalEntries === 0 ? this.metadataIndex.rebuild() : Promise.resolve(),
|
|
3989
|
-
hnswIndexSize === 0 ? this.index.rebuild() : Promise.resolve(),
|
|
3990
|
-
graphIndexSize === 0 ? this.graphIndex.rebuild() : Promise.resolve()
|
|
3991
|
-
]);
|
|
3992
|
-
const rebuildDuration = Date.now() - rebuildStartTime;
|
|
3993
|
-
if (!this.config.silent) {
|
|
3994
|
-
console.log(`✅ All indexes rebuilt in ${rebuildDuration}ms:\n` +
|
|
3995
|
-
` - Metadata: ${await this.metadataIndex.getStats().then(s => s.totalEntries)} entries\n` +
|
|
3996
|
-
` - HNSW Vector: ${this.index.size()} nodes\n` +
|
|
3997
|
-
` - Graph Adjacency: ${await this.graphIndex.size()} relationships\n` +
|
|
3998
|
-
` 💡 Indexes loaded from persisted storage (no recomputation)`);
|
|
3999
|
-
}
|
|
4106
|
+
// REBUILD: Either small dataset, forced rebuild, or explicit enable
|
|
4107
|
+
const rebuildReason = isLazyRebuild
|
|
4108
|
+
? '🔄 Lazy loading triggered by first query'
|
|
4109
|
+
: isSmallDataset
|
|
4110
|
+
? `🔄 Small dataset (${totalCount.toLocaleString()} items)`
|
|
4111
|
+
: '🔄 Auto-rebuild explicitly enabled';
|
|
4112
|
+
if (!this.config.silent) {
|
|
4113
|
+
console.log(`${rebuildReason} - rebuilding all indexes from persisted data...`);
|
|
4000
4114
|
}
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4115
|
+
// Rebuild all 3 indexes in parallel for performance
|
|
4116
|
+
// Indexes load their data from storage (no recomputation)
|
|
4117
|
+
const rebuildStartTime = Date.now();
|
|
4118
|
+
await Promise.all([
|
|
4119
|
+
metadataStats.totalEntries === 0 ? this.metadataIndex.rebuild() : Promise.resolve(),
|
|
4120
|
+
hnswIndexSize === 0 ? this.index.rebuild() : Promise.resolve(),
|
|
4121
|
+
graphIndexSize === 0 ? this.graphIndex.rebuild() : Promise.resolve()
|
|
4122
|
+
]);
|
|
4123
|
+
const rebuildDuration = Date.now() - rebuildStartTime;
|
|
4124
|
+
if (!this.config.silent) {
|
|
4125
|
+
console.log(`✅ All indexes rebuilt in ${rebuildDuration}ms:\n` +
|
|
4126
|
+
` - Metadata: ${await this.metadataIndex.getStats().then(s => s.totalEntries)} entries\n` +
|
|
4127
|
+
` - HNSW Vector: ${this.index.size()} nodes\n` +
|
|
4128
|
+
` - Graph Adjacency: ${await this.graphIndex.size()} relationships\n` +
|
|
4129
|
+
` 💡 Indexes loaded from persisted storage (no recomputation)`);
|
|
4007
4130
|
}
|
|
4008
4131
|
}
|
|
4009
4132
|
catch (error) {
|
|
@@ -925,9 +925,24 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
925
925
|
continue;
|
|
926
926
|
}
|
|
927
927
|
}
|
|
928
|
+
// v5.7.8: Convert connections from plain object to Map (JSON deserialization fix)
|
|
929
|
+
// When loaded from JSON, Map becomes plain object - must reconstruct
|
|
930
|
+
const connections = new Map();
|
|
931
|
+
if (noun.connections && typeof noun.connections === 'object') {
|
|
932
|
+
for (const [levelStr, ids] of Object.entries(noun.connections)) {
|
|
933
|
+
if (Array.isArray(ids)) {
|
|
934
|
+
connections.set(parseInt(levelStr, 10), new Set(ids));
|
|
935
|
+
}
|
|
936
|
+
else if (ids && typeof ids === 'object') {
|
|
937
|
+
// Handle if it's already an array-like or Set-like object
|
|
938
|
+
connections.set(parseInt(levelStr, 10), new Set(Object.values(ids)));
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
928
942
|
// Combine noun + metadata (v5.4.0: Extract standard fields to top-level)
|
|
929
943
|
collectedNouns.push({
|
|
930
944
|
...noun,
|
|
945
|
+
connections, // Use reconstructed Map instead of plain object
|
|
931
946
|
type: metadata.noun || type, // Required: Extract type from metadata
|
|
932
947
|
confidence: metadata.confidence,
|
|
933
948
|
weight: metadata.weight,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "5.7.
|
|
3
|
+
"version": "5.7.8",
|
|
4
4
|
"description": "Universal Knowledge Protocol™ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. Stage 3 CANONICAL: 42 nouns × 127 verbs covering 96-97% of all human knowledge.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|