@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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
504
|
+
hasMore: safeHasMore,
|
|
497
505
|
nextCursor: result.nextCursor
|
|
498
506
|
};
|
|
499
507
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|