@soulcraft/brainy 3.45.0 → 3.47.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 +191 -1
- package/README.md +23 -0
- package/dist/brainy.d.ts +13 -1
- package/dist/brainy.js +71 -12
- package/dist/hnsw/typeAwareHNSWIndex.d.ts +231 -0
- package/dist/hnsw/typeAwareHNSWIndex.js +439 -0
- package/dist/triple/TripleIntelligenceSystem.d.ts +3 -1
- package/dist/utils/metadataIndex.d.ts +59 -1
- package/dist/utils/metadataIndex.js +223 -2
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { MetadataIndexCache } from './metadataIndexCache.js';
|
|
7
7
|
import { prodLog } from './logger.js';
|
|
8
8
|
import { getGlobalCache } from './unifiedCache.js';
|
|
9
|
+
import { TypeUtils, NOUN_TYPE_COUNT, VERB_TYPE_COUNT } from '../types/graphTypes.js';
|
|
9
10
|
import { SparseIndex, ChunkManager, AdaptiveChunkingStrategy } from './metadataIndexChunking.js';
|
|
10
11
|
import { EntityIdMapper } from './entityIdMapper.js';
|
|
11
12
|
import { RoaringBitmap32 } from 'roaring-wasm';
|
|
@@ -27,6 +28,13 @@ export class MetadataIndexManager {
|
|
|
27
28
|
// Type-Field Affinity Tracking for intelligent NLP
|
|
28
29
|
this.typeFieldAffinity = new Map(); // nounType -> field -> count
|
|
29
30
|
this.totalEntitiesByType = new Map(); // nounType -> total count
|
|
31
|
+
// Phase 1b: Fixed-size type tracking (99.76% memory reduction vs Maps)
|
|
32
|
+
// Uint32Array provides O(1) access via type enum index
|
|
33
|
+
// 31 noun types × 4 bytes = 124 bytes (vs ~15KB with Map overhead)
|
|
34
|
+
// 40 verb types × 4 bytes = 160 bytes (vs ~20KB with Map overhead)
|
|
35
|
+
// Total: 284 bytes (vs ~35KB) = 99.2% memory reduction
|
|
36
|
+
this.entityCountsByTypeFixed = new Uint32Array(NOUN_TYPE_COUNT); // 124 bytes
|
|
37
|
+
this.verbCountsByTypeFixed = new Uint32Array(VERB_TYPE_COUNT); // 160 bytes
|
|
30
38
|
// File locking for concurrent write protection (prevents race conditions)
|
|
31
39
|
this.activeLocks = new Map();
|
|
32
40
|
this.lockPromises = new Map();
|
|
@@ -83,6 +91,9 @@ export class MetadataIndexManager {
|
|
|
83
91
|
async init() {
|
|
84
92
|
// Initialize EntityIdMapper (loads UUID ↔ integer mappings from storage)
|
|
85
93
|
await this.idMapper.init();
|
|
94
|
+
// Phase 1b: Sync loaded counts to fixed-size arrays
|
|
95
|
+
// This populates the Uint32Arrays from the Maps loaded by lazyLoadCounts()
|
|
96
|
+
this.syncTypeCountsToFixed();
|
|
86
97
|
// Warm the cache with common fields (v3.44.1 - lazy loading optimization)
|
|
87
98
|
await this.warmCache();
|
|
88
99
|
}
|
|
@@ -107,6 +118,50 @@ export class MetadataIndexManager {
|
|
|
107
118
|
}
|
|
108
119
|
}));
|
|
109
120
|
prodLog.debug('✅ Metadata cache warmed successfully');
|
|
121
|
+
// Phase 1b: Also warm cache for top types (type-aware optimization)
|
|
122
|
+
await this.warmCacheForTopTypes(3);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Phase 1b: Warm cache for top types (type-aware optimization)
|
|
126
|
+
* Preloads metadata indices for the most common entity types and their top fields
|
|
127
|
+
* This significantly improves query performance for the most frequently accessed data
|
|
128
|
+
*
|
|
129
|
+
* @param topN Number of top types to warm (default: 3)
|
|
130
|
+
*/
|
|
131
|
+
async warmCacheForTopTypes(topN = 3) {
|
|
132
|
+
// Get top noun types by entity count
|
|
133
|
+
const topTypes = this.getTopNounTypes(topN);
|
|
134
|
+
if (topTypes.length === 0) {
|
|
135
|
+
prodLog.debug('⏭️ Skipping type-aware cache warming: no types found yet');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
prodLog.debug(`🔥 Warming cache for top ${topTypes.length} types: ${topTypes.join(', ')}`);
|
|
139
|
+
// For each top type, warm cache for its top fields
|
|
140
|
+
for (const type of topTypes) {
|
|
141
|
+
// Get fields with high affinity to this type
|
|
142
|
+
const typeFields = this.typeFieldAffinity.get(type);
|
|
143
|
+
if (!typeFields)
|
|
144
|
+
continue;
|
|
145
|
+
// Sort fields by count (most common first)
|
|
146
|
+
const topFields = Array.from(typeFields.entries())
|
|
147
|
+
.sort((a, b) => b[1] - a[1])
|
|
148
|
+
.slice(0, 5) // Top 5 fields per type
|
|
149
|
+
.map(([field]) => field);
|
|
150
|
+
if (topFields.length === 0)
|
|
151
|
+
continue;
|
|
152
|
+
prodLog.debug(` 📊 Type '${type}' - warming fields: ${topFields.join(', ')}`);
|
|
153
|
+
// Preload sparse indices for these fields in parallel
|
|
154
|
+
await Promise.all(topFields.map(async (field) => {
|
|
155
|
+
try {
|
|
156
|
+
await this.loadSparseIndex(field);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
// Silently ignore if field doesn't exist yet
|
|
160
|
+
prodLog.debug(` ⏭️ Field '${field}' not yet indexed for type '${type}'`);
|
|
161
|
+
}
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
prodLog.debug('✅ Type-aware cache warming completed');
|
|
110
165
|
}
|
|
111
166
|
/**
|
|
112
167
|
* Acquire an in-memory lock for coordinating concurrent metadata index writes
|
|
@@ -186,6 +241,49 @@ export class MetadataIndexManager {
|
|
|
186
241
|
// This maintains zero-configuration principle
|
|
187
242
|
}
|
|
188
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Phase 1b: Sync Map-based counts to fixed-size Uint32Arrays
|
|
246
|
+
* This enables gradual migration from Maps to arrays while maintaining backward compatibility
|
|
247
|
+
* Called periodically and on demand to keep both representations in sync
|
|
248
|
+
*/
|
|
249
|
+
syncTypeCountsToFixed() {
|
|
250
|
+
// Sync noun counts from totalEntitiesByType Map to entityCountsByTypeFixed array
|
|
251
|
+
for (let i = 0; i < NOUN_TYPE_COUNT; i++) {
|
|
252
|
+
const type = TypeUtils.getNounFromIndex(i);
|
|
253
|
+
const count = this.totalEntitiesByType.get(type) || 0;
|
|
254
|
+
this.entityCountsByTypeFixed[i] = count;
|
|
255
|
+
}
|
|
256
|
+
// Sync verb counts from totalEntitiesByType Map to verbCountsByTypeFixed array
|
|
257
|
+
// Note: Verb counts are currently tracked alongside noun counts in totalEntitiesByType
|
|
258
|
+
// In the future, we may want a separate Map for verb counts
|
|
259
|
+
for (let i = 0; i < VERB_TYPE_COUNT; i++) {
|
|
260
|
+
const type = TypeUtils.getVerbFromIndex(i);
|
|
261
|
+
const count = this.totalEntitiesByType.get(type) || 0;
|
|
262
|
+
this.verbCountsByTypeFixed[i] = count;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Phase 1b: Sync from fixed-size arrays back to Maps (reverse direction)
|
|
267
|
+
* Used when Uint32Arrays are the source of truth and need to update Maps
|
|
268
|
+
*/
|
|
269
|
+
syncTypeCountsFromFixed() {
|
|
270
|
+
// Sync noun counts from array to Map
|
|
271
|
+
for (let i = 0; i < NOUN_TYPE_COUNT; i++) {
|
|
272
|
+
const count = this.entityCountsByTypeFixed[i];
|
|
273
|
+
if (count > 0) {
|
|
274
|
+
const type = TypeUtils.getNounFromIndex(i);
|
|
275
|
+
this.totalEntitiesByType.set(type, count);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Sync verb counts from array to Map
|
|
279
|
+
for (let i = 0; i < VERB_TYPE_COUNT; i++) {
|
|
280
|
+
const count = this.verbCountsByTypeFixed[i];
|
|
281
|
+
if (count > 0) {
|
|
282
|
+
const type = TypeUtils.getVerbFromIndex(i);
|
|
283
|
+
this.totalEntitiesByType.set(type, count);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
189
287
|
/**
|
|
190
288
|
* Update cardinality statistics for a field
|
|
191
289
|
*/
|
|
@@ -1348,6 +1446,102 @@ export class MetadataIndexManager {
|
|
|
1348
1446
|
getAllEntityCounts() {
|
|
1349
1447
|
return new Map(this.totalEntitiesByType);
|
|
1350
1448
|
}
|
|
1449
|
+
// ============================================================================
|
|
1450
|
+
// Phase 1b: Type Enum Methods (O(1) access via Uint32Arrays)
|
|
1451
|
+
// ============================================================================
|
|
1452
|
+
/**
|
|
1453
|
+
* Get entity count for a noun type using type enum (O(1) array access)
|
|
1454
|
+
* More efficient than Map-based getEntityCountByType
|
|
1455
|
+
* @param type Noun type from NounTypeEnum
|
|
1456
|
+
* @returns Count of entities of this type
|
|
1457
|
+
*/
|
|
1458
|
+
getEntityCountByTypeEnum(type) {
|
|
1459
|
+
const index = TypeUtils.getNounIndex(type);
|
|
1460
|
+
return this.entityCountsByTypeFixed[index];
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Get verb count for a verb type using type enum (O(1) array access)
|
|
1464
|
+
* @param type Verb type from VerbTypeEnum
|
|
1465
|
+
* @returns Count of verbs of this type
|
|
1466
|
+
*/
|
|
1467
|
+
getVerbCountByTypeEnum(type) {
|
|
1468
|
+
const index = TypeUtils.getVerbIndex(type);
|
|
1469
|
+
return this.verbCountsByTypeFixed[index];
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Get top N noun types by entity count (using fixed-size arrays)
|
|
1473
|
+
* Useful for type-aware cache warming and query optimization
|
|
1474
|
+
* @param n Number of top types to return
|
|
1475
|
+
* @returns Array of noun types sorted by count (highest first)
|
|
1476
|
+
*/
|
|
1477
|
+
getTopNounTypes(n) {
|
|
1478
|
+
const types = [];
|
|
1479
|
+
// Iterate through all noun types
|
|
1480
|
+
for (let i = 0; i < NOUN_TYPE_COUNT; i++) {
|
|
1481
|
+
const count = this.entityCountsByTypeFixed[i];
|
|
1482
|
+
if (count > 0) {
|
|
1483
|
+
const type = TypeUtils.getNounFromIndex(i);
|
|
1484
|
+
types.push({ type, count });
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
// Sort by count (descending) and return top N
|
|
1488
|
+
return types
|
|
1489
|
+
.sort((a, b) => b.count - a.count)
|
|
1490
|
+
.slice(0, n)
|
|
1491
|
+
.map(t => t.type);
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Get top N verb types by count (using fixed-size arrays)
|
|
1495
|
+
* @param n Number of top types to return
|
|
1496
|
+
* @returns Array of verb types sorted by count (highest first)
|
|
1497
|
+
*/
|
|
1498
|
+
getTopVerbTypes(n) {
|
|
1499
|
+
const types = [];
|
|
1500
|
+
// Iterate through all verb types
|
|
1501
|
+
for (let i = 0; i < VERB_TYPE_COUNT; i++) {
|
|
1502
|
+
const count = this.verbCountsByTypeFixed[i];
|
|
1503
|
+
if (count > 0) {
|
|
1504
|
+
const type = TypeUtils.getVerbFromIndex(i);
|
|
1505
|
+
types.push({ type, count });
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
// Sort by count (descending) and return top N
|
|
1509
|
+
return types
|
|
1510
|
+
.sort((a, b) => b.count - a.count)
|
|
1511
|
+
.slice(0, n)
|
|
1512
|
+
.map(t => t.type);
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Get all noun type counts as a Map (using fixed-size arrays)
|
|
1516
|
+
* More efficient than getAllEntityCounts for type-aware queries
|
|
1517
|
+
* @returns Map of noun type to count
|
|
1518
|
+
*/
|
|
1519
|
+
getAllNounTypeCounts() {
|
|
1520
|
+
const counts = new Map();
|
|
1521
|
+
for (let i = 0; i < NOUN_TYPE_COUNT; i++) {
|
|
1522
|
+
const count = this.entityCountsByTypeFixed[i];
|
|
1523
|
+
if (count > 0) {
|
|
1524
|
+
const type = TypeUtils.getNounFromIndex(i);
|
|
1525
|
+
counts.set(type, count);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
return counts;
|
|
1529
|
+
}
|
|
1530
|
+
/**
|
|
1531
|
+
* Get all verb type counts as a Map (using fixed-size arrays)
|
|
1532
|
+
* @returns Map of verb type to count
|
|
1533
|
+
*/
|
|
1534
|
+
getAllVerbTypeCounts() {
|
|
1535
|
+
const counts = new Map();
|
|
1536
|
+
for (let i = 0; i < VERB_TYPE_COUNT; i++) {
|
|
1537
|
+
const count = this.verbCountsByTypeFixed[i];
|
|
1538
|
+
if (count > 0) {
|
|
1539
|
+
const type = TypeUtils.getVerbFromIndex(i);
|
|
1540
|
+
counts.set(type, count);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
return counts;
|
|
1544
|
+
}
|
|
1351
1545
|
/**
|
|
1352
1546
|
* Get count of entities matching field-value criteria - queries chunked sparse index
|
|
1353
1547
|
*/
|
|
@@ -1763,7 +1957,17 @@ export class MetadataIndexManager {
|
|
|
1763
1957
|
typeFields.set(field, currentCount + 1);
|
|
1764
1958
|
// Update total entities of this type (only count once per entity)
|
|
1765
1959
|
if (field === 'noun') {
|
|
1766
|
-
|
|
1960
|
+
const newCount = this.totalEntitiesByType.get(entityType) + 1;
|
|
1961
|
+
this.totalEntitiesByType.set(entityType, newCount);
|
|
1962
|
+
// Phase 1b: Also update fixed-size array
|
|
1963
|
+
// Try to parse as noun type - if it matches a known type, update the array
|
|
1964
|
+
try {
|
|
1965
|
+
const nounTypeIndex = TypeUtils.getNounIndex(entityType);
|
|
1966
|
+
this.entityCountsByTypeFixed[nounTypeIndex] = newCount;
|
|
1967
|
+
}
|
|
1968
|
+
catch {
|
|
1969
|
+
// Not a recognized noun type, skip fixed-size array update
|
|
1970
|
+
}
|
|
1767
1971
|
}
|
|
1768
1972
|
}
|
|
1769
1973
|
else if (operation === 'remove') {
|
|
@@ -1779,11 +1983,28 @@ export class MetadataIndexManager {
|
|
|
1779
1983
|
if (field === 'noun') {
|
|
1780
1984
|
const total = this.totalEntitiesByType.get(entityType);
|
|
1781
1985
|
if (total > 1) {
|
|
1782
|
-
|
|
1986
|
+
const newCount = total - 1;
|
|
1987
|
+
this.totalEntitiesByType.set(entityType, newCount);
|
|
1988
|
+
// Phase 1b: Also update fixed-size array
|
|
1989
|
+
try {
|
|
1990
|
+
const nounTypeIndex = TypeUtils.getNounIndex(entityType);
|
|
1991
|
+
this.entityCountsByTypeFixed[nounTypeIndex] = newCount;
|
|
1992
|
+
}
|
|
1993
|
+
catch {
|
|
1994
|
+
// Not a recognized noun type, skip fixed-size array update
|
|
1995
|
+
}
|
|
1783
1996
|
}
|
|
1784
1997
|
else {
|
|
1785
1998
|
this.totalEntitiesByType.delete(entityType);
|
|
1786
1999
|
this.typeFieldAffinity.delete(entityType);
|
|
2000
|
+
// Phase 1b: Also zero out fixed-size array
|
|
2001
|
+
try {
|
|
2002
|
+
const nounTypeIndex = TypeUtils.getNounIndex(entityType);
|
|
2003
|
+
this.entityCountsByTypeFixed[nounTypeIndex] = 0;
|
|
2004
|
+
}
|
|
2005
|
+
catch {
|
|
2006
|
+
// Not a recognized noun type, skip fixed-size array update
|
|
2007
|
+
}
|
|
1787
2008
|
}
|
|
1788
2009
|
}
|
|
1789
2010
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.47.0",
|
|
4
4
|
"description": "Universal Knowledge Protocol™ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. 31 nouns × 40 verbs for infinite expressiveness.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|