@soulcraft/brainy 3.47.0 β†’ 3.47.1

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/dist/brainy.js CHANGED
@@ -2523,6 +2523,14 @@ export class Brainy {
2523
2523
  }
2524
2524
  return;
2525
2525
  }
2526
+ // OPTIMIZATION: Instant check - if index already has data, skip immediately
2527
+ // This gives 0s startup for warm restarts (vs 50-100ms of async checks)
2528
+ if (this.index.size() > 0) {
2529
+ if (!this.config.silent) {
2530
+ console.log(`βœ… Index already populated (${this.index.size().toLocaleString()} entities) - 0s startup!`);
2531
+ }
2532
+ return;
2533
+ }
2526
2534
  // BUG #2 FIX: Don't trust counts - check actual storage instead
2527
2535
  // Counts can be lost/corrupted in container restarts
2528
2536
  const entities = await this.storage.getNouns({ pagination: { limit: 1 } });
@@ -2543,31 +2551,31 @@ export class Brainy {
2543
2551
  graphIndexSize === 0 ||
2544
2552
  this.config.disableAutoRebuild === false; // Explicitly enabled
2545
2553
  if (!needsRebuild) {
2546
- // All indexes populated, no rebuild needed
2554
+ // All indexes already populated, no rebuild needed
2547
2555
  return;
2548
2556
  }
2549
2557
  // Small dataset: Rebuild all indexes for best performance
2550
2558
  if (totalCount < AUTO_REBUILD_THRESHOLD || this.config.disableAutoRebuild === false) {
2551
2559
  if (!this.config.silent) {
2552
2560
  console.log(this.config.disableAutoRebuild === false
2553
- ? 'πŸ”„ Auto-rebuild explicitly enabled - rebuilding all indexes...'
2554
- : `πŸ”„ Small dataset (${totalCount} items) - rebuilding all indexes...`);
2561
+ ? 'πŸ”„ Auto-rebuild explicitly enabled - rebuilding all indexes from persisted data...'
2562
+ : `πŸ”„ Small dataset (${totalCount} items) - rebuilding all indexes from persisted data...`);
2555
2563
  }
2556
- // BUG #1 FIX: Actually call graphIndex.rebuild()
2557
- // BUG #4 FIX: Actually call HNSW index.rebuild()
2558
2564
  // Rebuild all 3 indexes in parallel for performance
2559
- const startTime = Date.now();
2565
+ // Indexes load their data from storage (no recomputation)
2566
+ const rebuildStartTime = Date.now();
2560
2567
  await Promise.all([
2561
2568
  metadataStats.totalEntries === 0 ? this.metadataIndex.rebuild() : Promise.resolve(),
2562
2569
  hnswIndexSize === 0 ? this.index.rebuild() : Promise.resolve(),
2563
2570
  graphIndexSize === 0 ? this.graphIndex.rebuild() : Promise.resolve()
2564
2571
  ]);
2565
- const duration = Date.now() - startTime;
2572
+ const rebuildDuration = Date.now() - rebuildStartTime;
2566
2573
  if (!this.config.silent) {
2567
- console.log(`βœ… All indexes rebuilt in ${duration}ms:\n` +
2574
+ console.log(`βœ… All indexes rebuilt in ${rebuildDuration}ms:\n` +
2568
2575
  ` - Metadata: ${await this.metadataIndex.getStats().then(s => s.totalEntries)} entries\n` +
2569
2576
  ` - HNSW Vector: ${this.index.size()} nodes\n` +
2570
- ` - Graph Adjacency: ${await this.graphIndex.size()} relationships`);
2577
+ ` - Graph Adjacency: ${await this.graphIndex.size()} relationships\n` +
2578
+ ` πŸ’‘ Indexes loaded from persisted storage (no recomputation)`);
2571
2579
  }
2572
2580
  }
2573
2581
  else {
@@ -292,65 +292,121 @@ export class TypeAwareHNSWIndex {
292
292
  prodLog.warn('TypeAwareHNSW rebuild skipped: no storage adapter');
293
293
  return;
294
294
  }
295
+ const batchSize = options.batchSize || 1000;
295
296
  // Determine which types to rebuild
296
297
  const typesToRebuild = options.types || this.getAllNounTypes();
297
- prodLog.info(`Rebuilding ${typesToRebuild.length} type-aware HNSW indexes...`);
298
- const errors = [];
299
- // Rebuild each type's index with type-filtered pagination
298
+ prodLog.info(`Rebuilding ${typesToRebuild.length} type-aware HNSW indexes from persisted data...`);
299
+ // Clear all indexes we're rebuilding
300
300
  for (const type of typesToRebuild) {
301
- try {
302
- prodLog.info(`Rebuilding HNSW index for type: ${type}`);
303
- const index = this.getIndexForType(type);
304
- index.clear(); // Clear before rebuild
305
- // Load ONLY entities of this type from storage using pagination
306
- let cursor = undefined;
307
- let hasMore = true;
308
- let loaded = 0;
309
- while (hasMore) {
310
- // CRITICAL: Use type filtering to load only this type's entities
311
- const result = await this.storage.getNounsWithPagination({
312
- limit: options.batchSize || 1000,
313
- cursor,
314
- filter: { nounType: type } // ← TYPE FILTER!
315
- });
316
- // Add each entity to this type's index
317
- for (const noun of result.items) {
318
- try {
319
- await index.addItem({
320
- id: noun.id,
321
- vector: noun.vector
322
- });
323
- loaded++;
324
- if (options.onProgress) {
325
- options.onProgress(type, loaded, result.totalCount || loaded);
326
- }
327
- }
328
- catch (error) {
329
- prodLog.error(`Failed to add entity ${noun.id} to ${type} index:`, error);
330
- // Continue with other entities
301
+ const index = this.getIndexForType(type);
302
+ index.nouns.clear();
303
+ }
304
+ // Determine preloading strategy (adaptive caching) for entire dataset
305
+ const stats = await this.storage.getStatistics();
306
+ const entityCount = stats?.totalNodes || 0;
307
+ const vectorMemory = entityCount * 1536; // 384 dims Γ— 4 bytes
308
+ // Use first index's cache (they all share the same UnifiedCache)
309
+ const firstIndex = this.getIndexForType(typesToRebuild[0]);
310
+ const cacheStats = firstIndex.unifiedCache.getStats();
311
+ const availableCache = cacheStats.maxSize * 0.80;
312
+ const shouldPreload = vectorMemory < availableCache;
313
+ if (shouldPreload) {
314
+ prodLog.info(`HNSW: Preloading ${entityCount.toLocaleString()} vectors at init ` +
315
+ `(${(vectorMemory / 1024 / 1024).toFixed(1)}MB < ${(availableCache / 1024 / 1024).toFixed(1)}MB cache)`);
316
+ }
317
+ else {
318
+ prodLog.info(`HNSW: Adaptive caching for ${entityCount.toLocaleString()} vectors ` +
319
+ `(${(vectorMemory / 1024 / 1024).toFixed(1)}MB > ${(availableCache / 1024 / 1024).toFixed(1)}MB cache) - loading on-demand`);
320
+ }
321
+ // Load ALL nouns ONCE and route to correct type indexes
322
+ // This is O(N) instead of O(31*N) from the previous parallel approach
323
+ let cursor = undefined;
324
+ let hasMore = true;
325
+ let totalLoaded = 0;
326
+ const loadedByType = new Map();
327
+ while (hasMore) {
328
+ const result = await this.storage.getNounsWithPagination({
329
+ limit: batchSize,
330
+ cursor
331
+ });
332
+ // Route each noun to its type index
333
+ for (const nounData of result.items) {
334
+ try {
335
+ // Determine noun type from multiple possible sources
336
+ const nounType = nounData.nounType || nounData.metadata?.noun || nounData.metadata?.type;
337
+ // Skip if type not in rebuild list
338
+ if (!nounType || !typesToRebuild.includes(nounType)) {
339
+ continue;
340
+ }
341
+ // Get the index for this type
342
+ const index = this.getIndexForType(nounType);
343
+ // Load HNSW graph data
344
+ const hnswData = await this.storage.getHNSWData(nounData.id);
345
+ if (!hnswData) {
346
+ continue; // No HNSW data
347
+ }
348
+ // Create noun with restored connections
349
+ const noun = {
350
+ id: nounData.id,
351
+ vector: shouldPreload ? nounData.vector : [],
352
+ connections: new Map(),
353
+ level: hnswData.level
354
+ };
355
+ // Restore connections from storage
356
+ for (const [levelStr, nounIds] of Object.entries(hnswData.connections)) {
357
+ const level = parseInt(levelStr, 10);
358
+ noun.connections.set(level, new Set(nounIds));
359
+ }
360
+ // Add to type-specific index
361
+ ;
362
+ index.nouns.set(nounData.id, noun);
363
+ // Track high-level nodes
364
+ if (noun.level >= 2 && noun.level <= index.MAX_TRACKED_LEVELS) {
365
+ if (!index.highLevelNodes.has(noun.level)) {
366
+ ;
367
+ index.highLevelNodes.set(noun.level, new Set());
331
368
  }
369
+ ;
370
+ index.highLevelNodes.get(noun.level).add(nounData.id);
332
371
  }
333
- hasMore = result.hasMore;
334
- cursor = result.nextCursor;
372
+ // Track progress
373
+ loadedByType.set(nounType, (loadedByType.get(nounType) || 0) + 1);
374
+ totalLoaded++;
375
+ if (options.onProgress && totalLoaded % 100 === 0) {
376
+ options.onProgress(nounType, loadedByType.get(nounType) || 0, totalLoaded);
377
+ }
378
+ }
379
+ catch (error) {
380
+ prodLog.error(`Failed to restore HNSW data for ${nounData.id}:`, error);
335
381
  }
336
- prodLog.info(`βœ… Rebuilt ${type} index: ${index.size().toLocaleString()} entities`);
337
382
  }
338
- catch (error) {
339
- prodLog.error(`Failed to rebuild ${type} index:`, error);
340
- errors.push({ type, error: error });
341
- // Continue with other types instead of failing completely
383
+ hasMore = result.hasMore;
384
+ cursor = result.nextCursor;
385
+ // Progress logging
386
+ if (totalLoaded % 1000 === 0) {
387
+ prodLog.info(`Progress: ${totalLoaded.toLocaleString()} entities loaded...`);
342
388
  }
343
389
  }
344
- // Report errors at end
345
- if (errors.length > 0) {
346
- const failedTypes = errors.map((e) => e.type).join(', ');
347
- prodLog.warn(`⚠️ Failed to rebuild ${errors.length} type indexes: ${failedTypes}`);
348
- // Throw if ALL rebuilds failed
349
- if (errors.length === typesToRebuild.length) {
350
- throw new Error('All type-aware HNSW rebuilds failed');
390
+ // Restore entry points for each type
391
+ for (const type of typesToRebuild) {
392
+ const index = this.getIndexForType(type);
393
+ let maxLevel = 0;
394
+ let entryPointId = null;
395
+ for (const [id, noun] of index.nouns.entries()) {
396
+ if (noun.level > maxLevel) {
397
+ maxLevel = noun.level;
398
+ entryPointId = id;
399
+ }
351
400
  }
401
+ ;
402
+ index.entryPointId = entryPointId;
403
+ index.maxLevel = maxLevel;
404
+ const loaded = loadedByType.get(type) || 0;
405
+ const cacheInfo = shouldPreload ? ' (vectors preloaded)' : ' (adaptive caching)';
406
+ prodLog.info(`βœ… Rebuilt ${type} index: ${loaded.toLocaleString()} entities, ` +
407
+ `${maxLevel + 1} levels, entry point: ${entryPointId || 'none'}${cacheInfo}`);
352
408
  }
353
- prodLog.info(`βœ… TypeAwareHNSW rebuild complete: ${this.size().toLocaleString()} total entities across ${this.indexes.size} types`);
409
+ prodLog.info(`βœ… TypeAwareHNSW rebuild complete: ${this.size().toLocaleString()} total entities across ${this.indexes.size} types (loaded from persisted graph structure)`);
354
410
  }
355
411
  /**
356
412
  * Get comprehensive statistics
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/brainy",
3
- "version": "3.47.0",
3
+ "version": "3.47.1",
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",