@soulcraft/brainy 3.1.0 → 3.1.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
@@ -16,6 +16,7 @@ import { NaturalLanguageProcessor } from './neural/naturalLanguageProcessor.js';
16
16
  import { TripleIntelligenceSystem } from './triple/TripleIntelligenceSystem.js';
17
17
  import { MetadataIndexManager } from './utils/metadataIndex.js';
18
18
  import { GraphAdjacencyIndex } from './graph/graphAdjacencyIndex.js';
19
+ import { configureLogger, LogLevel } from './utils/logger.js';
19
20
  import { NounType } from './types/graphTypes.js';
20
21
  /**
21
22
  * The main Brainy class - Clean, Beautiful, Powerful
@@ -50,13 +51,22 @@ export class Brainy {
50
51
  storage: { ...this.config.storage, ...configOverrides.storage },
51
52
  model: { ...this.config.model, ...configOverrides.model },
52
53
  index: { ...this.config.index, ...configOverrides.index },
53
- augmentations: { ...this.config.augmentations, ...configOverrides.augmentations }
54
+ augmentations: { ...this.config.augmentations, ...configOverrides.augmentations },
55
+ verbose: configOverrides.verbose ?? this.config.verbose,
56
+ silent: configOverrides.silent ?? this.config.silent
54
57
  };
55
58
  // Set dimensions if provided
56
59
  if (dimensions) {
57
60
  this.dimensions = dimensions;
58
61
  }
59
62
  }
63
+ // Configure logging based on config options
64
+ if (this.config.silent) {
65
+ configureLogger({ level: -1 }); // Suppress all logs
66
+ }
67
+ else if (this.config.verbose) {
68
+ configureLogger({ level: LogLevel.DEBUG }); // Enable verbose logging
69
+ }
60
70
  try {
61
71
  // Setup and initialize storage
62
72
  this.storage = await this.setupStorage();
@@ -1190,7 +1200,9 @@ export class Brainy {
1190
1200
  warmup: config?.warmup ?? false,
1191
1201
  realtime: config?.realtime ?? false,
1192
1202
  multiTenancy: config?.multiTenancy ?? false,
1193
- telemetry: config?.telemetry ?? false
1203
+ telemetry: config?.telemetry ?? false,
1204
+ verbose: config?.verbose ?? false,
1205
+ silent: config?.silent ?? false
1194
1206
  };
1195
1207
  }
1196
1208
  /**
@@ -4040,5 +4040,7 @@ export const PATTERNS_METADATA = {
4040
4040
  total: 403493
4041
4041
  }
4042
4042
  };
4043
- console.log(`🧠 Brainy Pattern Library loaded: ${EMBEDDED_PATTERNS.length} patterns, ${(PATTERNS_METADATA.sizeBytes.total / 1024).toFixed(1)}KB total`);
4043
+ // Only log if not suppressed - controlled by logging configuration
4044
+ import { prodLog } from '../utils/logger.js';
4045
+ prodLog.info(`🧠 Brainy Pattern Library loaded: ${EMBEDDED_PATTERNS.length} patterns, ${(PATTERNS_METADATA.sizeBytes.total / 1024).toFixed(1)}KB total`);
4044
4046
  //# sourceMappingURL=embeddedPatterns.js.map
@@ -503,8 +503,23 @@ export class FileSystemStorage extends BaseStorage {
503
503
  }
504
504
  // Get page of files
505
505
  const pageFiles = nounFiles.slice(startIndex, startIndex + limit);
506
- // Load nouns
506
+ // Load nouns - count actual successfully loaded items
507
507
  const items = [];
508
+ let successfullyLoaded = 0;
509
+ let totalValidFiles = 0;
510
+ // First pass: count total valid files (for accurate totalCount)
511
+ // This is necessary to fix the pagination bug
512
+ for (const file of nounFiles) {
513
+ try {
514
+ // Just check if file exists and is readable
515
+ await fs.promises.access(path.join(this.nounsDir, file), fs.constants.R_OK);
516
+ totalValidFiles++;
517
+ }
518
+ catch {
519
+ // File not readable, skip
520
+ }
521
+ }
522
+ // Second pass: load the current page
508
523
  for (const file of pageFiles) {
509
524
  try {
510
525
  const data = await fs.promises.readFile(path.join(this.nounsDir, file), 'utf-8');
@@ -523,18 +538,21 @@ export class FileSystemStorage extends BaseStorage {
523
538
  continue;
524
539
  }
525
540
  items.push(noun);
541
+ successfullyLoaded++;
526
542
  }
527
543
  catch (error) {
528
544
  console.warn(`Failed to read noun file ${file}:`, error);
529
545
  }
530
546
  }
531
- const hasMore = startIndex + limit < nounFiles.length;
547
+ // CRITICAL FIX: hasMore should be based on actual valid files, not just file count
548
+ // Also check if we actually loaded any items from this page
549
+ const hasMore = (startIndex + limit < totalValidFiles) && (successfullyLoaded > 0 || startIndex === 0);
532
550
  const nextCursor = hasMore && pageFiles.length > 0
533
551
  ? pageFiles[pageFiles.length - 1].replace('.json', '')
534
552
  : undefined;
535
553
  return {
536
554
  items,
537
- totalCount: nounFiles.length,
555
+ totalCount: totalValidFiles, // Use actual valid file count, not all files
538
556
  hasMore,
539
557
  nextCursor
540
558
  };
@@ -345,10 +345,14 @@ export class BaseStorage extends BaseStorageAdapter {
345
345
  });
346
346
  // Apply offset if needed (some adapters might not support offset)
347
347
  const items = result.items.slice(offset);
348
+ // CRITICAL SAFETY CHECK: Prevent infinite loops
349
+ // If we have no items but hasMore is true, force hasMore to false
350
+ // This prevents pagination bugs from causing infinite loops
351
+ const safeHasMore = items.length > 0 ? result.hasMore : false;
348
352
  return {
349
353
  items,
350
354
  totalCount: result.totalCount || totalCount,
351
- hasMore: result.hasMore,
355
+ hasMore: safeHasMore,
352
356
  nextCursor: result.nextCursor
353
357
  };
354
358
  }
@@ -490,10 +494,14 @@ export class BaseStorage extends BaseStorageAdapter {
490
494
  });
491
495
  // Apply offset if needed (some adapters might not support offset)
492
496
  const items = result.items.slice(offset);
497
+ // CRITICAL SAFETY CHECK: Prevent infinite loops
498
+ // If we have no items but hasMore is true, force hasMore to false
499
+ // This prevents pagination bugs from causing infinite loops
500
+ const safeHasMore = items.length > 0 ? result.hasMore : false;
493
501
  return {
494
502
  items,
495
503
  totalCount: result.totalCount || totalCount,
496
- hasMore: result.hasMore,
504
+ hasMore: safeHasMore,
497
505
  nextCursor: result.nextCursor
498
506
  };
499
507
  }
@@ -273,6 +273,8 @@ export interface BrainyConfig {
273
273
  realtime?: boolean;
274
274
  multiTenancy?: boolean;
275
275
  telemetry?: boolean;
276
+ verbose?: boolean;
277
+ silent?: boolean;
276
278
  }
277
279
  /**
278
280
  * Neural similarity parameters
@@ -1250,10 +1250,31 @@ export class MetadataIndexManager {
1250
1250
  const nounLimit = 25; // Even smaller batches during initialization to prevent socket exhaustion
1251
1251
  let hasMoreNouns = true;
1252
1252
  let totalNounsProcessed = 0;
1253
- while (hasMoreNouns) {
1253
+ let consecutiveEmptyBatches = 0;
1254
+ const MAX_ITERATIONS = 10000; // Safety limit to prevent infinite loops
1255
+ let iterations = 0;
1256
+ while (hasMoreNouns && iterations < MAX_ITERATIONS) {
1257
+ iterations++;
1254
1258
  const result = await this.storage.getNouns({
1255
1259
  pagination: { offset: nounOffset, limit: nounLimit }
1256
1260
  });
1261
+ // CRITICAL SAFETY CHECK: Prevent infinite loop on empty results
1262
+ if (result.items.length === 0) {
1263
+ consecutiveEmptyBatches++;
1264
+ if (consecutiveEmptyBatches >= 3) {
1265
+ prodLog.warn('⚠️ Breaking metadata rebuild loop: received 3 consecutive empty batches');
1266
+ break;
1267
+ }
1268
+ // If hasMore is true but items are empty, it's likely a bug
1269
+ if (result.hasMore) {
1270
+ prodLog.warn(`⚠️ Storage returned empty items but hasMore=true at offset ${nounOffset}`);
1271
+ hasMoreNouns = false; // Force exit
1272
+ break;
1273
+ }
1274
+ }
1275
+ else {
1276
+ consecutiveEmptyBatches = 0; // Reset counter on non-empty batch
1277
+ }
1257
1278
  // CRITICAL FIX: Use batch metadata reading to prevent socket exhaustion
1258
1279
  const nounIds = result.items.map(noun => noun.id);
1259
1280
  let metadataBatch;
@@ -1315,10 +1336,30 @@ export class MetadataIndexManager {
1315
1336
  const verbLimit = 25; // Even smaller batches during initialization to prevent socket exhaustion
1316
1337
  let hasMoreVerbs = true;
1317
1338
  let totalVerbsProcessed = 0;
1318
- while (hasMoreVerbs) {
1339
+ let consecutiveEmptyVerbBatches = 0;
1340
+ let verbIterations = 0;
1341
+ while (hasMoreVerbs && verbIterations < MAX_ITERATIONS) {
1342
+ verbIterations++;
1319
1343
  const result = await this.storage.getVerbs({
1320
1344
  pagination: { offset: verbOffset, limit: verbLimit }
1321
1345
  });
1346
+ // CRITICAL SAFETY CHECK: Prevent infinite loop on empty results
1347
+ if (result.items.length === 0) {
1348
+ consecutiveEmptyVerbBatches++;
1349
+ if (consecutiveEmptyVerbBatches >= 3) {
1350
+ prodLog.warn('⚠️ Breaking verb metadata rebuild loop: received 3 consecutive empty batches');
1351
+ break;
1352
+ }
1353
+ // If hasMore is true but items are empty, it's likely a bug
1354
+ if (result.hasMore) {
1355
+ prodLog.warn(`⚠️ Storage returned empty verb items but hasMore=true at offset ${verbOffset}`);
1356
+ hasMoreVerbs = false; // Force exit
1357
+ break;
1358
+ }
1359
+ }
1360
+ else {
1361
+ consecutiveEmptyVerbBatches = 0; // Reset counter on non-empty batch
1362
+ }
1322
1363
  // CRITICAL FIX: Use batch verb metadata reading to prevent socket exhaustion
1323
1364
  const verbIds = result.items.map(verb => verb.id);
1324
1365
  let verbMetadataBatch;
@@ -1372,6 +1413,13 @@ export class MetadataIndexManager {
1372
1413
  }
1373
1414
  await this.yieldToEventLoop();
1374
1415
  }
1416
+ // Check if we hit iteration limits
1417
+ if (iterations >= MAX_ITERATIONS) {
1418
+ prodLog.error(`❌ Metadata noun rebuild hit maximum iteration limit (${MAX_ITERATIONS}). This indicates a bug in storage pagination.`);
1419
+ }
1420
+ if (verbIterations >= MAX_ITERATIONS) {
1421
+ prodLog.error(`❌ Metadata verb rebuild hit maximum iteration limit (${MAX_ITERATIONS}). This indicates a bug in storage pagination.`);
1422
+ }
1375
1423
  // Flush to storage with final yield
1376
1424
  prodLog.debug('💾 Flushing metadata index to storage...');
1377
1425
  await this.flush();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/brainy",
3
- "version": "3.1.0",
3
+ "version": "3.1.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",