@soulcraft/brainy 6.2.1 → 6.2.3
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 +6 -0
- package/dist/brainy.js +2 -3
- package/dist/hnsw/hnswIndex.d.ts +6 -0
- package/dist/hnsw/hnswIndex.js +81 -8
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -3
- package/dist/storage/adapters/azureBlobStorage.js +9 -1
- package/dist/storage/adapters/gcsStorage.js +8 -1
- package/dist/storage/adapters/s3CompatibleStorage.js +7 -2
- package/dist/storage/baseStorage.d.ts +12 -0
- package/dist/storage/baseStorage.js +16 -0
- package/dist/triple/TripleIntelligenceSystem.d.ts +1 -2
- package/dist/utils/metadataIndex.d.ts +6 -2
- package/dist/utils/metadataIndex.js +31 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
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
|
+
### [6.2.2](https://github.com/soulcraftlabs/brainy/compare/v6.2.1...v6.2.2) (2025-11-25)
|
|
6
|
+
|
|
7
|
+
- refactor: remove 3,700+ LOC of unused HNSW implementations (e3146ce)
|
|
8
|
+
- fix(hnsw): entry point recovery prevents import failures and log spam (52eae67)
|
|
9
|
+
|
|
10
|
+
|
|
5
11
|
## [6.2.0](https://github.com/soulcraftlabs/brainy/compare/v6.1.0...v6.2.0) (2025-11-20)
|
|
6
12
|
|
|
7
13
|
### ⚡ Critical Performance Fix
|
package/dist/brainy.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { v4 as uuidv4 } from './universal/uuid.js';
|
|
8
8
|
import { HNSWIndex } from './hnsw/hnswIndex.js';
|
|
9
|
-
import { HNSWIndexOptimized } from './hnsw/hnswIndexOptimized.js';
|
|
10
9
|
import { TypeAwareHNSWIndex } from './hnsw/typeAwareHNSWIndex.js';
|
|
11
10
|
import { createStorage } from './storage/storageFactory.js';
|
|
12
11
|
import { defaultEmbeddingFunction, cosineDistance } from './utils/index.js';
|
|
@@ -802,7 +801,7 @@ export class Brainy {
|
|
|
802
801
|
if (this.index instanceof TypeAwareHNSWIndex && metadata.noun) {
|
|
803
802
|
tx.addOperation(new RemoveFromTypeAwareHNSWOperation(this.index, id, noun.vector, metadata.noun));
|
|
804
803
|
}
|
|
805
|
-
else if (this.index instanceof HNSWIndex
|
|
804
|
+
else if (this.index instanceof HNSWIndex) {
|
|
806
805
|
tx.addOperation(new RemoveFromHNSWOperation(this.index, id, noun.vector));
|
|
807
806
|
}
|
|
808
807
|
}
|
|
@@ -1887,7 +1886,7 @@ export class Brainy {
|
|
|
1887
1886
|
if (this.index instanceof TypeAwareHNSWIndex && metadata.noun) {
|
|
1888
1887
|
tx.addOperation(new RemoveFromTypeAwareHNSWOperation(this.index, id, noun.vector, metadata.noun));
|
|
1889
1888
|
}
|
|
1890
|
-
else if (this.index instanceof HNSWIndex
|
|
1889
|
+
else if (this.index instanceof HNSWIndex) {
|
|
1891
1890
|
tx.addOperation(new RemoveFromHNSWOperation(this.index, id, noun.vector));
|
|
1892
1891
|
}
|
|
1893
1892
|
}
|
package/dist/hnsw/hnswIndex.d.ts
CHANGED
|
@@ -83,6 +83,12 @@ export declare class HNSWIndex {
|
|
|
83
83
|
* Add a vector to the index
|
|
84
84
|
*/
|
|
85
85
|
addItem(item: VectorDocument): Promise<string>;
|
|
86
|
+
/**
|
|
87
|
+
* O(1) entry point recovery using highLevelNodes index (v6.2.3).
|
|
88
|
+
* At any reasonable scale (1000+ nodes), level 2+ nodes are guaranteed to exist.
|
|
89
|
+
* For tiny indexes with only level 0-1 nodes, any node works as entry point.
|
|
90
|
+
*/
|
|
91
|
+
private recoverEntryPointO1;
|
|
86
92
|
/**
|
|
87
93
|
* Search for nearest neighbors
|
|
88
94
|
*/
|
package/dist/hnsw/hnswIndex.js
CHANGED
|
@@ -210,9 +210,8 @@ export class HNSWIndex {
|
|
|
210
210
|
}
|
|
211
211
|
// Find entry point
|
|
212
212
|
if (!this.entryPointId) {
|
|
213
|
-
|
|
214
|
-
//
|
|
215
|
-
// This is a safety check
|
|
213
|
+
// No entry point but nouns exist - corrupted state, recover by using this item
|
|
214
|
+
// This shouldn't normally happen as first item sets entry point above
|
|
216
215
|
this.entryPointId = id;
|
|
217
216
|
this.maxLevel = nounLevel;
|
|
218
217
|
this.nouns.set(id, noun);
|
|
@@ -220,7 +219,7 @@ export class HNSWIndex {
|
|
|
220
219
|
}
|
|
221
220
|
const entryPoint = this.nouns.get(this.entryPointId);
|
|
222
221
|
if (!entryPoint) {
|
|
223
|
-
|
|
222
|
+
// Entry point was deleted but ID not updated - recover by using new item
|
|
224
223
|
// If the entry point doesn't exist, treat this as the first noun
|
|
225
224
|
this.entryPointId = id;
|
|
226
225
|
this.maxLevel = nounLevel;
|
|
@@ -383,6 +382,27 @@ export class HNSWIndex {
|
|
|
383
382
|
}
|
|
384
383
|
return id;
|
|
385
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* O(1) entry point recovery using highLevelNodes index (v6.2.3).
|
|
387
|
+
* At any reasonable scale (1000+ nodes), level 2+ nodes are guaranteed to exist.
|
|
388
|
+
* For tiny indexes with only level 0-1 nodes, any node works as entry point.
|
|
389
|
+
*/
|
|
390
|
+
recoverEntryPointO1() {
|
|
391
|
+
// O(1) recovery: check highLevelNodes from highest to lowest level
|
|
392
|
+
for (let level = this.MAX_TRACKED_LEVELS; level >= 2; level--) {
|
|
393
|
+
const nodesAtLevel = this.highLevelNodes.get(level);
|
|
394
|
+
if (nodesAtLevel && nodesAtLevel.size > 0) {
|
|
395
|
+
for (const nodeId of nodesAtLevel) {
|
|
396
|
+
if (this.nouns.has(nodeId)) {
|
|
397
|
+
return { id: nodeId, level };
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// No high-level nodes - use any available node (works fine for HNSW)
|
|
403
|
+
const firstNode = this.nouns.keys().next().value;
|
|
404
|
+
return { id: firstNode ?? null, level: 0 };
|
|
405
|
+
}
|
|
386
406
|
/**
|
|
387
407
|
* Search for nearest neighbors
|
|
388
408
|
*/
|
|
@@ -398,14 +418,33 @@ export class HNSWIndex {
|
|
|
398
418
|
throw new Error(`Query vector dimension mismatch: expected ${this.dimension}, got ${queryVector.length}`);
|
|
399
419
|
}
|
|
400
420
|
// Start from the entry point
|
|
421
|
+
// If entry point is null but nouns exist, attempt O(1) recovery (v6.2.3)
|
|
422
|
+
if (!this.entryPointId && this.nouns.size > 0) {
|
|
423
|
+
const { id: recoveredId, level: recoveredLevel } = this.recoverEntryPointO1();
|
|
424
|
+
if (recoveredId) {
|
|
425
|
+
this.entryPointId = recoveredId;
|
|
426
|
+
this.maxLevel = recoveredLevel;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
401
429
|
if (!this.entryPointId) {
|
|
402
|
-
|
|
430
|
+
// Truly empty index - return empty results silently
|
|
403
431
|
return [];
|
|
404
432
|
}
|
|
405
|
-
|
|
433
|
+
let entryPoint = this.nouns.get(this.entryPointId);
|
|
406
434
|
if (!entryPoint) {
|
|
407
|
-
|
|
408
|
-
|
|
435
|
+
// Entry point ID exists but noun was deleted - O(1) recovery (v6.2.3)
|
|
436
|
+
if (this.nouns.size > 0) {
|
|
437
|
+
const { id: recoveredId, level: recoveredLevel } = this.recoverEntryPointO1();
|
|
438
|
+
if (recoveredId) {
|
|
439
|
+
this.entryPointId = recoveredId;
|
|
440
|
+
this.maxLevel = recoveredLevel;
|
|
441
|
+
entryPoint = this.nouns.get(recoveredId);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// If still no entry point, return empty
|
|
445
|
+
if (!entryPoint) {
|
|
446
|
+
return [];
|
|
447
|
+
}
|
|
409
448
|
}
|
|
410
449
|
let currObj = entryPoint;
|
|
411
450
|
// OPTIMIZATION: Preload entry point vector
|
|
@@ -915,6 +954,40 @@ export class HNSWIndex {
|
|
|
915
954
|
offset += batchSize; // v5.7.11: Increment offset for next page
|
|
916
955
|
}
|
|
917
956
|
}
|
|
957
|
+
// Step 5: CRITICAL - Recover entry point if missing (v6.2.3 - O(1))
|
|
958
|
+
// This ensures consistency even if getHNSWSystem() returned null
|
|
959
|
+
if (this.nouns.size > 0 && this.entryPointId === null) {
|
|
960
|
+
prodLog.warn('HNSW rebuild: Entry point was null after loading nouns - recovering with O(1) lookup');
|
|
961
|
+
const { id: recoveredId, level: recoveredLevel } = this.recoverEntryPointO1();
|
|
962
|
+
this.entryPointId = recoveredId;
|
|
963
|
+
this.maxLevel = recoveredLevel;
|
|
964
|
+
prodLog.info(`HNSW entry point recovered: ${recoveredId} at level ${recoveredLevel}`);
|
|
965
|
+
// Persist recovered state to prevent future recovery
|
|
966
|
+
if (this.storage && recoveredId) {
|
|
967
|
+
await this.storage.saveHNSWSystem({
|
|
968
|
+
entryPointId: this.entryPointId,
|
|
969
|
+
maxLevel: this.maxLevel
|
|
970
|
+
}).catch((error) => {
|
|
971
|
+
prodLog.error('Failed to persist recovered HNSW system data:', error);
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
// Step 6: Validate entry point exists if set (handles stale/deleted entry point)
|
|
976
|
+
if (this.entryPointId && !this.nouns.has(this.entryPointId)) {
|
|
977
|
+
prodLog.warn(`HNSW: Entry point ${this.entryPointId} not found in loaded nouns - recovering with O(1) lookup`);
|
|
978
|
+
const { id: recoveredId, level: recoveredLevel } = this.recoverEntryPointO1();
|
|
979
|
+
this.entryPointId = recoveredId;
|
|
980
|
+
this.maxLevel = recoveredLevel;
|
|
981
|
+
// Persist corrected state
|
|
982
|
+
if (this.storage && recoveredId) {
|
|
983
|
+
await this.storage.saveHNSWSystem({
|
|
984
|
+
entryPointId: this.entryPointId,
|
|
985
|
+
maxLevel: this.maxLevel
|
|
986
|
+
}).catch((error) => {
|
|
987
|
+
prodLog.error('Failed to persist corrected HNSW system data:', error);
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
}
|
|
918
991
|
const cacheInfo = shouldPreload
|
|
919
992
|
? ` (vectors preloaded)`
|
|
920
993
|
: ` (adaptive caching - vectors loaded on-demand)`;
|
package/dist/index.d.ts
CHANGED
|
@@ -51,9 +51,8 @@ export { StorageAugmentation, DynamicStorageAugmentation, MemoryStorageAugmentat
|
|
|
51
51
|
export { WebSocketConduitAugmentation };
|
|
52
52
|
import type { Vector, VectorDocument, SearchResult, DistanceFunction, EmbeddingFunction, EmbeddingModel, HNSWNoun, HNSWVerb, HNSWConfig, StorageAdapter } from './coreTypes.js';
|
|
53
53
|
import { HNSWIndex } from './hnsw/hnswIndex.js';
|
|
54
|
-
|
|
55
|
-
export {
|
|
56
|
-
export type { Vector, VectorDocument, SearchResult, DistanceFunction, EmbeddingFunction, EmbeddingModel, HNSWNoun, HNSWVerb, HNSWConfig, HNSWOptimizedConfig, StorageAdapter };
|
|
54
|
+
export { HNSWIndex };
|
|
55
|
+
export type { Vector, VectorDocument, SearchResult, DistanceFunction, EmbeddingFunction, EmbeddingModel, HNSWNoun, HNSWVerb, HNSWConfig, StorageAdapter };
|
|
57
56
|
import type { AugmentationResponse, BrainyAugmentation, BaseAugmentation, AugmentationContext } from './types/augmentations.js';
|
|
58
57
|
export { AugmentationManager, type AugmentationInfo } from './augmentationManager.js';
|
|
59
58
|
export type { AugmentationResponse, BrainyAugmentation, BaseAugmentation, AugmentationContext };
|
package/dist/index.js
CHANGED
|
@@ -111,10 +111,9 @@ MemoryStorageAugmentation, FileSystemStorageAugmentation, OPFSStorageAugmentatio
|
|
|
111
111
|
createAutoStorageAugmentation, createStorageAugmentationFromConfig };
|
|
112
112
|
// Other augmentation exports
|
|
113
113
|
export { WebSocketConduitAugmentation };
|
|
114
|
-
// Export HNSW index
|
|
114
|
+
// Export HNSW index
|
|
115
115
|
import { HNSWIndex } from './hnsw/hnswIndex.js';
|
|
116
|
-
|
|
117
|
-
export { HNSWIndex, HNSWIndexOptimized };
|
|
116
|
+
export { HNSWIndex };
|
|
118
117
|
// Export augmentation manager for type-safe augmentation management
|
|
119
118
|
export { AugmentationManager } from './augmentationManager.js';
|
|
120
119
|
import { NounType, VerbType } from './types/graphTypes.js';
|
|
@@ -1269,7 +1269,15 @@ export class AzureBlobStorage extends BaseStorage {
|
|
|
1269
1269
|
return JSON.parse(downloaded.toString());
|
|
1270
1270
|
}
|
|
1271
1271
|
catch (error) {
|
|
1272
|
-
|
|
1272
|
+
// Azure may return not found errors in different formats
|
|
1273
|
+
const isNotFound = error.statusCode === 404 ||
|
|
1274
|
+
error.code === 'BlobNotFound' ||
|
|
1275
|
+
error.code === 404 ||
|
|
1276
|
+
error.details?.code === 'BlobNotFound' ||
|
|
1277
|
+
error.message?.includes('BlobNotFound') ||
|
|
1278
|
+
error.message?.includes('not found') ||
|
|
1279
|
+
error.message?.includes('404');
|
|
1280
|
+
if (isNotFound) {
|
|
1273
1281
|
return null;
|
|
1274
1282
|
}
|
|
1275
1283
|
this.logger.error('Failed to get HNSW system data:', error);
|
|
@@ -1260,7 +1260,14 @@ export class GcsStorage extends BaseStorage {
|
|
|
1260
1260
|
return JSON.parse(contents.toString());
|
|
1261
1261
|
}
|
|
1262
1262
|
catch (error) {
|
|
1263
|
-
|
|
1263
|
+
// GCS may return 404 in different formats depending on SDK version
|
|
1264
|
+
const is404 = error.code === 404 ||
|
|
1265
|
+
error.statusCode === 404 ||
|
|
1266
|
+
error.status === 404 ||
|
|
1267
|
+
error.message?.includes('No such object') ||
|
|
1268
|
+
error.message?.includes('not found') ||
|
|
1269
|
+
error.message?.includes('404');
|
|
1270
|
+
if (is404) {
|
|
1264
1271
|
return null;
|
|
1265
1272
|
}
|
|
1266
1273
|
this.logger.error('Failed to get HNSW system data:', error);
|
|
@@ -2829,9 +2829,14 @@ export class S3CompatibleStorage extends BaseStorage {
|
|
|
2829
2829
|
return JSON.parse(bodyContents);
|
|
2830
2830
|
}
|
|
2831
2831
|
catch (error) {
|
|
2832
|
-
|
|
2832
|
+
// S3 may return not found errors in different formats
|
|
2833
|
+
const isNotFound = error.name === 'NoSuchKey' ||
|
|
2834
|
+
error.code === 'NoSuchKey' ||
|
|
2835
|
+
error.$metadata?.httpStatusCode === 404 ||
|
|
2833
2836
|
error.message?.includes('NoSuchKey') ||
|
|
2834
|
-
error.message?.includes('not found')
|
|
2837
|
+
error.message?.includes('not found') ||
|
|
2838
|
+
error.message?.includes('404');
|
|
2839
|
+
if (isNotFound) {
|
|
2835
2840
|
return null;
|
|
2836
2841
|
}
|
|
2837
2842
|
this.logger.error('Failed to get HNSW system data:', error);
|
|
@@ -618,6 +618,18 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
|
|
|
618
618
|
* Periodically called when counts are updated
|
|
619
619
|
*/
|
|
620
620
|
protected saveTypeStatistics(): Promise<void>;
|
|
621
|
+
/**
|
|
622
|
+
* Get noun counts by type (O(1) access to type statistics)
|
|
623
|
+
* v6.2.2: Exposed for MetadataIndexManager to use as single source of truth
|
|
624
|
+
* @returns Uint32Array indexed by NounType enum value (42 types)
|
|
625
|
+
*/
|
|
626
|
+
getNounCountsByType(): Uint32Array;
|
|
627
|
+
/**
|
|
628
|
+
* Get verb counts by type (O(1) access to type statistics)
|
|
629
|
+
* v6.2.2: Exposed for MetadataIndexManager to use as single source of truth
|
|
630
|
+
* @returns Uint32Array indexed by VerbType enum value (127 types)
|
|
631
|
+
*/
|
|
632
|
+
getVerbCountsByType(): Uint32Array;
|
|
621
633
|
/**
|
|
622
634
|
* Rebuild type counts from actual storage (v5.5.0)
|
|
623
635
|
* Called when statistics are missing or inconsistent
|
|
@@ -1983,6 +1983,22 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
1983
1983
|
};
|
|
1984
1984
|
await this.writeObjectToPath(`${SYSTEM_DIR}/type-statistics.json`, stats);
|
|
1985
1985
|
}
|
|
1986
|
+
/**
|
|
1987
|
+
* Get noun counts by type (O(1) access to type statistics)
|
|
1988
|
+
* v6.2.2: Exposed for MetadataIndexManager to use as single source of truth
|
|
1989
|
+
* @returns Uint32Array indexed by NounType enum value (42 types)
|
|
1990
|
+
*/
|
|
1991
|
+
getNounCountsByType() {
|
|
1992
|
+
return this.nounCountsByType;
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Get verb counts by type (O(1) access to type statistics)
|
|
1996
|
+
* v6.2.2: Exposed for MetadataIndexManager to use as single source of truth
|
|
1997
|
+
* @returns Uint32Array indexed by VerbType enum value (127 types)
|
|
1998
|
+
*/
|
|
1999
|
+
getVerbCountsByType() {
|
|
2000
|
+
return this.verbCountsByType;
|
|
2001
|
+
}
|
|
1986
2002
|
/**
|
|
1987
2003
|
* Rebuild type counts from actual storage (v5.5.0)
|
|
1988
2004
|
* Called when statistics are missing or inconsistent
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
* - Fusion: O(k log k) where k = result count
|
|
14
14
|
*/
|
|
15
15
|
import { HNSWIndex } from '../hnsw/hnswIndex.js';
|
|
16
|
-
import { HNSWIndexOptimized } from '../hnsw/hnswIndexOptimized.js';
|
|
17
16
|
import { TypeAwareHNSWIndex } from '../hnsw/typeAwareHNSWIndex.js';
|
|
18
17
|
import { MetadataIndexManager } from '../utils/metadataIndex.js';
|
|
19
18
|
import { Vector } from '../coreTypes.js';
|
|
@@ -68,7 +67,7 @@ export declare class TripleIntelligenceSystem {
|
|
|
68
67
|
private planner;
|
|
69
68
|
private embedder;
|
|
70
69
|
private storage;
|
|
71
|
-
constructor(metadataIndex: MetadataIndexManager, hnswIndex: HNSWIndex |
|
|
70
|
+
constructor(metadataIndex: MetadataIndexManager, hnswIndex: HNSWIndex | TypeAwareHNSWIndex, graphIndex: GraphAdjacencyIndex, embedder: (text: string) => Promise<Vector>, storage: any);
|
|
72
71
|
/**
|
|
73
72
|
* Main find method - executes Triple Intelligence queries
|
|
74
73
|
* Phase 3: Now with automatic type inference for 40% latency reduction
|
|
@@ -112,8 +112,9 @@ export declare class MetadataIndexManager {
|
|
|
112
112
|
*/
|
|
113
113
|
private releaseLock;
|
|
114
114
|
/**
|
|
115
|
-
* Lazy load entity counts from
|
|
116
|
-
*
|
|
115
|
+
* Lazy load entity counts from the 'noun' field sparse index (O(n) where n = number of types)
|
|
116
|
+
* v6.2.2 FIX: Previously read from stats.nounCount which was SERVICE-keyed, not TYPE-keyed
|
|
117
|
+
* Now computes counts from the sparse index which has the correct type information
|
|
117
118
|
*/
|
|
118
119
|
private lazyLoadCounts;
|
|
119
120
|
/**
|
|
@@ -434,6 +435,9 @@ export declare class MetadataIndexManager {
|
|
|
434
435
|
getTotalEntityCount(): number;
|
|
435
436
|
/**
|
|
436
437
|
* Get all entity types and their counts - O(1) operation
|
|
438
|
+
* v6.2.2: Fixed - totalEntitiesByType is correctly populated by updateTypeFieldAffinity
|
|
439
|
+
* during add operations. lazyLoadCounts was reading wrong data but that doesn't
|
|
440
|
+
* affect freshly-added entities within the same session.
|
|
437
441
|
*/
|
|
438
442
|
getAllEntityCounts(): Map<string, number>;
|
|
439
443
|
/**
|
|
@@ -84,8 +84,9 @@ export class MetadataIndexManager {
|
|
|
84
84
|
this.chunkingStrategy = new AdaptiveChunkingStrategy();
|
|
85
85
|
// Initialize Field Type Inference (v3.48.0)
|
|
86
86
|
this.fieldTypeInference = new FieldTypeInference(storage);
|
|
87
|
-
//
|
|
88
|
-
|
|
87
|
+
// v6.2.2: Removed lazyLoadCounts() call from constructor
|
|
88
|
+
// It was a race condition (not awaited) and read from wrong source.
|
|
89
|
+
// Now properly called in init() after warmCache() loads the sparse index.
|
|
89
90
|
}
|
|
90
91
|
/**
|
|
91
92
|
* Initialize the metadata index manager
|
|
@@ -97,11 +98,15 @@ export class MetadataIndexManager {
|
|
|
97
98
|
await this.loadFieldRegistry();
|
|
98
99
|
// Initialize EntityIdMapper (loads UUID ↔ integer mappings from storage)
|
|
99
100
|
await this.idMapper.init();
|
|
100
|
-
// Phase 1b: Sync loaded counts to fixed-size arrays
|
|
101
|
-
// This populates the Uint32Arrays from the Maps loaded by lazyLoadCounts()
|
|
102
|
-
this.syncTypeCountsToFixed();
|
|
103
101
|
// Warm the cache with common fields (v3.44.1 - lazy loading optimization)
|
|
102
|
+
// This loads the 'noun' sparse index which is needed for type counts
|
|
104
103
|
await this.warmCache();
|
|
104
|
+
// v6.2.2: Load type counts AFTER warmCache (sparse index is now cached)
|
|
105
|
+
// Previously called in constructor without await and read from wrong source
|
|
106
|
+
await this.lazyLoadCounts();
|
|
107
|
+
// Phase 1b: Sync loaded counts to fixed-size arrays
|
|
108
|
+
// Now correctly happens AFTER lazyLoadCounts() finishes
|
|
109
|
+
this.syncTypeCountsToFixed();
|
|
105
110
|
}
|
|
106
111
|
/**
|
|
107
112
|
* Warm the cache by preloading common field sparse indices (v3.44.1)
|
|
@@ -226,25 +231,34 @@ export class MetadataIndexManager {
|
|
|
226
231
|
this.activeLocks.delete(lockKey);
|
|
227
232
|
}
|
|
228
233
|
/**
|
|
229
|
-
* Lazy load entity counts from
|
|
230
|
-
*
|
|
234
|
+
* Lazy load entity counts from the 'noun' field sparse index (O(n) where n = number of types)
|
|
235
|
+
* v6.2.2 FIX: Previously read from stats.nounCount which was SERVICE-keyed, not TYPE-keyed
|
|
236
|
+
* Now computes counts from the sparse index which has the correct type information
|
|
231
237
|
*/
|
|
232
238
|
async lazyLoadCounts() {
|
|
233
239
|
try {
|
|
234
|
-
//
|
|
235
|
-
const
|
|
236
|
-
if (
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
240
|
+
// v6.2.2: Load counts from sparse index (correct source)
|
|
241
|
+
const nounSparseIndex = await this.loadSparseIndex('noun');
|
|
242
|
+
if (!nounSparseIndex) {
|
|
243
|
+
// No sparse index yet - counts will be populated as entities are added
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// Iterate through all chunks and sum up bitmap sizes by type
|
|
247
|
+
for (const chunkId of nounSparseIndex.getAllChunkIds()) {
|
|
248
|
+
const chunk = await this.chunkManager.loadChunk('noun', chunkId);
|
|
249
|
+
if (chunk) {
|
|
250
|
+
for (const [type, bitmap] of chunk.entries) {
|
|
251
|
+
const currentCount = this.totalEntitiesByType.get(type) || 0;
|
|
252
|
+
this.totalEntitiesByType.set(type, currentCount + bitmap.size);
|
|
241
253
|
}
|
|
242
254
|
}
|
|
243
255
|
}
|
|
256
|
+
prodLog.debug(`✅ Loaded type counts from sparse index: ${this.totalEntitiesByType.size} types`);
|
|
244
257
|
}
|
|
245
258
|
catch (error) {
|
|
246
259
|
// Silently fail - counts will be populated as entities are added
|
|
247
260
|
// This maintains zero-configuration principle
|
|
261
|
+
prodLog.debug('Could not load type counts from sparse index:', error);
|
|
248
262
|
}
|
|
249
263
|
}
|
|
250
264
|
/**
|
|
@@ -1875,6 +1889,9 @@ export class MetadataIndexManager {
|
|
|
1875
1889
|
}
|
|
1876
1890
|
/**
|
|
1877
1891
|
* Get all entity types and their counts - O(1) operation
|
|
1892
|
+
* v6.2.2: Fixed - totalEntitiesByType is correctly populated by updateTypeFieldAffinity
|
|
1893
|
+
* during add operations. lazyLoadCounts was reading wrong data but that doesn't
|
|
1894
|
+
* affect freshly-added entities within the same session.
|
|
1878
1895
|
*/
|
|
1879
1896
|
getAllEntityCounts() {
|
|
1880
1897
|
return new Map(this.totalEntitiesByType);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "6.2.
|
|
3
|
+
"version": "6.2.3",
|
|
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",
|